第38回 DLLプログラミング(誰も教えてくれないDirectX作法) 99/5/10
最近、DLLをモジュール的に使っているソフトに出会って、久々に感動を覚えた。それが、プレステエミュであることは、ここでは言わないでおこう。(思いっきり言うとるがな、おっさん...)
Windowsで、DLL化した、外部プログラムを呼び出すには、LoadLibraryしたのちに、GetProcAddressとかゆー関数で、DLL内の関数への所在を調べて呼ぶだけで良いわけだが、GetProcAddressで関数名を、、DLLでファイル名で指定しているところらへんがミソで、これにより、(関数名と引数さえわかっていれば)任意のDLLを必要に応じて呼び出すことが出来る。これは、画像ビュアーSusie等でも古くから使われている手法である。
欠点
☆ ひとつひとつの関数をGetProcAddressするのが面倒
☆ 結局は、関数宣言をあらかじめ規定しておく必要がある
ということで使う気になれなかった。まあ、プラグインやモジュールの開発(OpenGLを使用する描画モジュール,DirectXを使用する描画モジュールなんてのがある場合)のような、
☆ インターフェースが単純
☆ 後で拡張したり差し替えたりする必要があるもの
には、向いてるということである。
しかし最大の問題はやねぇ、DLL内のグローバル変数を直接アクセスする手段が無いっちゅーことなんよ。いやー、メモリマップドファイルとか何とかゆーの使ったりすればいいんだろうけど、なんでグローバル変数をDLL_EXPORTできないんやろねー。メモリマップドファイルって、なんかメモリ空間まるごと見せまっせーなので、とても扱いにくいやん?
まあ、そのへんの制限が気にいらなければ、ActiveXを素直に使ったほうが賢明なんだろうけど、そんなことゆうてもやねぇ、ActiveX用の雛型作るだけでも覚えんとあかんことぎょーさんありまっせー。こんな、業界標準になるのかどうかさえも怪しいようなテクノロジーに深入りするのは危険なので、ちゃっちゃと身を引き、DLLの呼び出しについてもう少し考察する。
と、言うのも、DirectXを使う以上、dsound.dll等のdllは、実行時にLoadLibraryして読み込むべきだと思うからである。何故、そうする必要があるかというと、dllが見つからない場合、プログラム開始前にエラーが出て落ちてしまうため、その対策用の処理を用意することが出来ないからである。よって、市販のゲーム等では、DirectX関連のdllは、LoadLibraryして読み込むのは、常識である。(守ってないソフトも多々あるが)
ただ、LoadLibraryのあと、GetProcAddressでひとつひとつの関数のアドレスを得なければならないのが、何ともダサイというか、醜悪というか、やねうらおの趣味ではない。(まあ、DirectXのオブジェクトは、COMオブジェクトになっているんで、GetProcAddressしなければならない関数というのは、たかだか数個なので許すとしよう...)
第39回 DLLプログラミング2(なぜか載っていないDLL技法) 99/5/11
さっそく前回のつづき。今日、いろんなWindowsの書籍を探したが、LoadLibraryしたDLL側の変数にアクセスするのは、そう簡単ではないのではないかという結論に達した。なんで、classをまるごとDLL_EXPORTできんかなー。それがActiveXだっちゅー話もあるが、どこまで普及するかもわからんような先行き不安なテクノロジーを勉強しようとは思わんのよねー。
どうせ、__declspec(dllexport)なんてマイクロソフト独自の拡張してんだから、そいつをclass対応にするぐらいわけないのにねー。そんなわけで、しばらく英文のナレッジデータベースを調べていると、classは、__declspec(dllexport)/__declspec(dllimport)できるけど、関数はDLL呼び出し側でも定義しなきゃだめよーんって書いてあるではないか。そなもんなー、呼び出し側で定義するんやったら意味あらへんがな。純粋仮想関数ならかまへんって書いてあるけど、おんどれそしたら、こいつDLL側で継承して、そのポインタをダウンキャストして返せばええとでもゆうんかい!?などと英文マニュアルに関西風ツッコミを入れたところで、無意味ちゃんだろう。(なんか、日本語めちゃめちゃやな..)
DirectXは、このへんどないして回避してるかっちゅーと、DirectXはCOMインターフェースだから、そのへん気にしなくてもいいのだ。dsound.hを見ればわかるが、実体はclass(内部に関数を含む)ではなく、struct(内部に関数を含まない)で、かつ、関数呼び出しの部分は、COMインターフェースとして実装してあるから、何の問題もないのである。ActiveXの初歩の初歩なんだけど、それ以上の役割って、ActiveXにあるとは思えないのだが...。
そんなわけで、実践的には、何も考えずGetProcAddressしまくって、かつ、関数の引数でclassを受け渡したくても、我慢の子というわけである。それが嫌ならActiveX使えばーってことだろう。マイクロソフトよ。お前の言いたいことはようわかった。お前とは、金輪際限りじゃ。もう親でもなければ子でもねえー!(なんのこっちゃ)
第3A回 WindowsAPI(ついに買っちゃった...) 99/5/16
開発専用でWindowsNTマシンを購入した。翔泳社のWindowsバイブル1〜4やDirectX解説書、Windowsプログラミングの本等、WindowsNTマシンと同じぐらい書籍代に使ってしまった。糞馬鹿Windowsなんか捨てて、Linuxするんやー!とか言ってた矢先なので、この豹変ぶりには回りの人も驚いていた。詳しいことは言えないが、「臭作」に似たゲームで8月ごろ発売なのである。
当初は、インターネット上を検索して、プログラミングのホームページ等からReGetでファイルを根こそぎもらってきていたりしたのだが、欲しい情報を完全に網羅しているページが無いのである。(当たり前と言えば当たり前だが..) 初心者向けの講座は多いが、ある程度突っ込んだ内容となると数が途端に少なくなる。まー、世のなか、そんなもんなのかも知れない。えーい、こんなんちまちま検索してたら時間がいくらあっても足りない!!と、悟りをひらいた(?)やねうらおは、日本橋に参考図書を買いに行ったのである。あそこで、金をケチっていたら、いまごろ胃に大きな穴が空いて入院費用のほうが高くついていたことだろう...。
それはともかく。
NTでも動くようにDirectX3対応のライブラリを一から構築中しなくてはならない。フルスクリーン/ウィンドゥ表示切り替えとか、パレット制御とか、ファイル圧縮とか、CD再生とか、ゲームとはある意味、無関係のプログラムがてんこもりで、下手すると、そっちの方が大変なんではないかなーとか思わんでもない。
WindowsAPIバイブルは、オンラインマニュアルよりはマシだけど、一冊1万近くもするのにこの出来はひどいと思う。そもそも、MFCがもっと洗練された方法でWin32APIをラップして、かつ、親切なオンラインマニュアルさえつけておいてくれれば、こんな糞な書籍を馬鹿ほど買う必要なんてなかったのである。
そんなことを考えるに、あらためてWindowsプログラミングの敷居の高さを実感したのである。これだけ資料を買いそろえなくてはならないのだとしたら、ちょっと勉強してみよかい!なんて気には到底なれないのである。いま、ここにあるWindowsプログラミング関連の書籍のページ数をすべて足すと、2万数ページである。ひっくり返らないほうが不思議である。やねうらおにしても、今回の仕事さえ引き受けていなければ、やーい、Windowsのばーかばーか悔しかったらここまでおいでーとか言ってアカンベーしてLinuxに逃げていたことだろう。
そんなわけで、ここ数日は、WindowsのAPIバイブルとDirectXの書籍をにらめっこする日々が続いているわけである。なんか、この連載でWindows関連で、えらく嘘を書いていることにも気づいたが、まあ、それはそれで良しとしよう。これだけ書籍を読まなければわからないのでは、単なるミステイクというよりは、不可避なトラブルとぐらいに考えたほうが健全である。
ともかく...。DirectX3対応のゲーム用ライブラリが完成したら、ソースを公開する予定である。それによって、Windowsプログラムの敷居が少しでも低くなれば幸いである。
第3B回 プログラマー養成ギブス(How to ゲームプログラマー) 99/5/18
最近、プログラム初心者の高校生にC言語プログラミングを教える機会があった。とりあえず、やねうら流は、そんなに甘くはない。まずは、適性検査から始める。適性検査は、こうである。
まず、猫でもわかるプログラミング
http://www2m.biglobe.ne.jp/~yasutaka/index.html
の記事をすべて読めと。DirectX関連記事として、Bio100%の掲載記事
http://www.and.or.jp/Bio_100/article/index.html
に目を通せと。あとCプログラミング診断室
http://www.pro.or.jp/~fuji/mybooks/cdiag/index.html
に行って、診断でも受けてこいと。お手軽にゲーム作成のために、まずはel
http://www3.justnet.ne.jp/~botchy/index.html
だろうよと。とりあえず、こんだけを一週間でこないしてきたら、僕が問題を出すからそれに答えられたらゲームプログラミングを教えてやろうとか何とか言って、とりあえず、その場はしのいだのである。まー、なんか知らんけど、必死でやりよったねー(笑) これだけの量をさせるんだから、僕が相当の難問を出すんだろうと思って、オンラインヘルプを駆使して相当勉強したようである。
しかし、そんな期待(?)を見事に裏切り、僕の出した問題は、これだったのである:
(問)「int型配列a[4]に対し、この配列に格納されている数値を降順に並び替えるプログラムを書け」という設問の模範回答と非模範的回答を示せ。
なんじゃこりゃー。と言われるかも知れないが、これが、やねうら流である。まー、プライバシーのため彼の回答はここでは控えるが、ちょっと期待はずれな回答であった。まー、彼もまた、勉強の仕方ちゅーもんを間違っているような気がする。勉強の仕方なんてもんは、100人いれば、100通りの方法があって然るべきなのだが、時間をかけてもあるレベルまで達していないのならば、やはりどこか間違ったやり方をしているような気がする。
一応、参考までに、上の問いの答えに対する判定基準を書いておくが、ソートするだけならアルゴリズムなんていまさら言うに及ばず、バブルソートでもクイックソートでも何を持ち出してきてもらっても結構なのである。Cにはqsortなんて言うクイックソート専用の関数もあるし、forないし、whileでループさせてバブルソートしても構わない。その場合の比較回数や計算オーダーがいくらいくらですよと言ったところで、絵に描いた餅だろうし、そもそも初心者の彼がそういった一般化を出来るとは、はなっから期待していない。そういうのは、この問題の答えを予め知っている人間の回答であって、この問題について考えたことにはならない。そういう回答は、今回はすべて0点である。それに、そんなものは、せいぜい「アルゴリズム+データ構造=プログラム」というような古典で語られる、(やねうらおに言わせれば)頭の悪い人たちのパラダイムでしかない。(しかし、それが不要だと思っているわけではない)
では、プログラマにとって、必要な適性とは何だろうか?問題を一般化する能力だろうか?確かに、一般化する能力は必要である。この問題のint型の部分が他の型に変わったときのためにテンプレートにするだとか、大小比較する部分をコールバック関数で定義するだとか、4がもっと大きな定数Nに変わったときのためにクイックソートのアルゴリズムについて検討するだとか、そういう能力は必要である。問題をメタレベルまで引き上げて検討する力がなければ、新しい理論なんて生まれてくることはないのだから。
しかし、それはそれ。そんなことをやれとは書いていないし、まして、初心者の彼に望むべくもない。おまけに、Nにまで一般化して初心者である自分の首をしめることもない。たった4要素なのだから、if文を並べるだけ並べて、「これが最善である」と言うのなら、それを認めてやるべきだろうし、whileでループするのが最善でifを並べるのはプログラムのステップ数が増えるし、一般的ではないという理由で非模範的というのならば、それもまた正しい。しかし、どういった回答であれ、模範的と思うものと、非模範的と思うもの、それぞれのメリットとデメリットをあげて比較考察する力は欲しいのである。それは、プログラミング以前の段階の論理思考能力の問題なのだから...。
第3C回 メッセージループについて考える(WinMainの謎) 99/5/20
言うまでもなく、Windowsはイベントドリブン型の制御を行なう。何かイベントが発生して、そのときにどうするかというのをプログラムして行く。C++でWindowsのプログラムを開発しているとそのイベントをどんどん吸い出して、処理するループが必要である。それがメッセージループである。
どうも、やねうらおは頭が悪いのか、このメッセージループというものをいまだよく理解できないでいる。真面目なWindowsのプログラミング入門書を見ると必ず書いてある。最初のページで、ウィンドゥ作成まではええがな。その次のページに載ってるメッセージループって何やねん。いきなり敷居高いやんけ〜。とか思ってしまう。そもそも、メッセージループがやねー、いるっちゅーことは、メインのプログラムは一体、どこに書いたらええねのんや?こんなことをどこかのプログラミングの掲示板ででも質問しようものなら「お前、Windows入門書、1から勉強せえ!」と袋叩きに合うのは目に見えているが、本当にそのへんのところを詳しく知りたいのである。
ほとんどのWindows書籍SetTimerでタイマメッセージを発生させて、それに応じて画面描画をするようになっているが、これは、実は疑問の構想だ。
まず、画面描画ルーチンは、いまタイトル画面であるかとか、ゲーム画面であるかとか判断する必要が出てきて、case文の固まりになってしまう。case文が出てくるのが嫌だから、WM_PAINTのハンドラはやね、関数ポインタにしておいて、次のシーンに行くごとにそいつを更新してやれば良いのだが、あまり美しくないし、プログラムがリニアに書けない以上、あとで修正・追加するのは大変である。そもそも、WM_TIMERは精度が不安定かつ、優先順位が低いのだ。ゲーム用に使えやしねぇ。
まあ、マルチメディアタイマのコールバック関数timeSetEventを使えば、SetTimerより高精度ではあるが、あまり短いピリオドで呼ぶとイベントが重なって処理落ちが発生したりする。一般的には、この方法が良いのかも知れないが、やはり、リニアにプログラムを書けないのではなんだか気持ち悪い。(しかし、Win32で(=Windows3.1との互換を保って)動かすようなアプリの作成をするなら、これが妥当な方法のような気がする)
そんなわけで、やねうらおが考えたのは、メッセージループはぐるぐる回らせといて、まーそこでは描画イベントぐらいだけを処理するようにして、ゲーム中の処理はThreadを生成し、そこでだらだらと書くというスタイルである。一応、BM98のときは、うまく動いてくれていた。
スレッドはCreateThreadで作っても良いが、それだとスレッド関数の中ではCの関数で使用出来ない物が発生するなどの制限もあるので、_beginthread(Windows95)か_beginthreadNT(WindowsNT)で作成すべきのようである。どうも、ランタイムライブラリの初期化が正常に行なわれないらしい(なんちゅー仕様じゃ。しかも95とNT使いわけんとあかんとは...) 次回は、このへんについて、もうちっと突っ込んだ話をする。(やっとゲームプログラムの話らしゅうなってきたな〜とか何とか)
第3D回 メッセージループについて考える2(つづき!) 99/5/21
いきなりだが前回、最後に書いた_beginThreadNTという関数は存在しない。BM98のときは、CreateThreadで実現していたのだが、今回、ゲーム用のSDKを作り直すに当たり、そのへんを翔泳社のAPIバイブル1で調べたところ、CreateThreadはCのランタイムの初期化せーへんから_beginThreadか_beginThreadNT使ってやーって書いてあったのを信用して前回の記事を書いたのだが、いざ、使おうと思ってOnLineマニュアルでそのへんのことを調べたら_beginThreadNTなんて載っちゃいねぇ。だ、騙された!
もしやと思ってVC++のincludeパスを検索するが、_beginThreadNTなんてありゃしねぇ。そもそも、_beginThreadはNT対応のようである。どうなってんの?まあ、それでいいなら、それでいいんだけど...
さて。メッセージループについて、さらに検討していくが、今回は、まずelのメッセージループはどうなってんのかを調べる。elって何?って方は、この連載の最初のほうを読み返すか、本家
http://www3.justnet.ne.jp/~botchy/index.html
を見るべし。
さて。まず、elのWinMainを検索。どうやら、elMain_A内で定義されている。スクリーンセーバーモード等のために3種類のメインを#defineで用意しているのだろう。ざっと追っていくと、elLoop(メッセージループを作るためのマクロ)のなかでelSetScreenしなければならないようだが、こいつは、
#define elSetScreen(No,Function)\
case No:\
{\
Function;\
\
break;\
}
と定義されている。つまり、メッセージループ内で、処理するメッセージが無いときは、こいつがcaseブロックに展開されて、スクリーン番号に応じたシーン(処理)が呼び出されるというもののようである。(どうも、このへん、スクリーン番号ではなく、Idle時に処理する関数ポインタのようなものを使ったほうがcase文が増えてきたときに速いような気もするのだが、連続した番号のcase文はテーブルジャンプに最適化されるので、構わないという判断なのだろう)
ともかく。el方式は、微小化されたプログラムのフラグメント(断片)を集積していくやりかたである。これでもいいような気もするが、メーリングリストで「メインのプログラムは一体、どこに書いたらええねのんや?」と質問して袋叩きに合うタイプのやねうらおは、その手のプログラムは好きくない。好きくないったら、好きくないのである!
第3E回 DirectXのお勧めの参考書(悪い本と悪い本と悪い本!?) 99/5/22
最初に言っておくが、DirectXで人にお勧めできる本なんて無い!すべて腐っている。と書いて終わりたいところですが、またもやプログラマ志望のしとたちが路頭に迷う(笑)ので、今回はまじめに紹介します。
一番、信頼性が高いのはマイクロソフト公式マニュアル。高いけれど、適当なサンプルソースで穴埋めをしただけのような本を買うよりはよっぽど価値がある。ただ、DirectX5の日本語オンラインマニュアルは入手できたはずなので、それを印刷すれば済むと思う(ページ数多いけど) 決してこんな本、買わないように。(DirectX6のオフィシャルマニュアルなんて18,000円+税だもんなー。ホビープログラマーに、こんなもん買わすなって...)
「Direct3Dプログラミングガイド」これは、清水さんのやつ。彼の3D好きは業界でも有名だ(笑) 彼は、Direct3D用のコードをアセンブラでカリカリ書かせたら日本で一番良質のコードを書くと思う。しかし、本には、たいしたことは書いていない。(失礼ごめん>清水さん) どちらかと言えば3D初心者向け。Direct3Dの初期化ライブラリをいち早く開発した功績で本を執筆できたのだと僕は認識している。彼自身、大学在学中に執筆したものだから、仕方ないと言えば仕方ないのだが、もう少し突っ込んだことを書いても良かったんでないかなー。
「DirectX実践プログラミング」工学社。この本は、糞です。高額社もとい工学社は、他にもDirectDrawとDirect3Dの本をI/O別冊という形で出していたと思いますが、かなり糞です。オンラインマニュアルの要点をまとめて、SDKのサンプルをぶっこ抜いただけに近いものがあります。まあ、この手の専門書にしては2,500円と安いので、買う価値はあるかも知れませんが。
「DirectX5 ゲームプログラミング入門」インプレス/アクロバイト監訳。入門という割には、網羅的に要所要所を書いてあるので、ちょっと読み解くのに時間が掛かるかも知れません。そもそも、DirectX3の解説本なのに、DirectX3をCDに収録して、DirectX5ゲームプログラミング入門というタイトルをつけるあたり詐欺くさい気がしないでもないですが(笑)、DirectX関連では、まず、これ一冊あれば十分かなーという気がします。4,800円+税です。ただし、この手のDirectX関連の本から、Waveを読むためのマルチメディア関数(mmioなんとか)とか、ビットマップの内部構造とか、COMオブジェクトについてだとか、DirectXとは無関係の部分まで学ぼうとしないこと。それは、それ専門の本を読んだほうがよっぽどわかりやすいし、為になります。
「DirectDrawプログラミング」翔泳社/平林監訳。翔泳社の書籍は高いというイメージが強いのですが、記事はたいてい一流プログラマの書籍を翻訳していることが多く、正確さ、わかりやすさの点で私は一目置いています。すでに、DirectXの専門書を6,7冊持っていたにもかかわらず、これも買っちゃいました(笑) なんちゅーか、DirectDrawのスタンダードなテキストって感じですね。
とりあえず、DirectX関連の書籍はそんだけ。でもSDKのサンプルソースぐらい労を惜しまずに読もうね。
第3F回 アプリを終了しているのにタスクバーに残るやん!(同じ現象でお悩みのあなたに) 99/5/23
またもやWindowsにやられた。市販のアプリを作っているもんで、標題のような恥ずかしい現象を知っていて残しておくわけにはいかず、1時間ほどかけて必死にトレースした。トレースしてもしても原因がまったくつかめない。
何のことかわからない人に説明をするとBM98を始めとして、DirectXを使ったゲームアプリでは、ときどき終了してもタスクバーに箱が残るという現象がある。コンシューマーからの移植ものに多い。以前から気になっていたのだが、今回、このバグの原因をついに突き止めた。それは、Windows側に問題がある(ような気がする)
いくらトレースしてもプログラムは終わっているのである。終わっているにもかかわらず終わらないのは、なぜかというと、Cのランタイム(?)が、このアプリに制御が移るのを待っているからである。最初、なんでこんな処理が入っているのかわからなかったが、ともかく入っているのである。制御が移るのを待つと言っても、さっきまでゲームスレッドがWM_CLOSEポスト→メッセージハンドラでDirectX終了処理 & DestroyWindow→WM_DESTROY発生→メッセージハンドラでPostQuitMessage→WM_QUIT発生→メッセージループから脱出と、ここまで処理してたんだから、制御は、このアプリにありそうなものである。ところが、このアプリにないのだ!
どうも、DestroyWindowのバグ(?)のような気がするのだが、こいつ、終了時に、内部的にShowWindow(hWnd,SW_SHOWMINIMIZE);するようである。要するに、ウィンドウの最小化である。(えっ?もう僕が言いたいことがわかった?) そう。最小化されたアプリは、入力フォーカスを持てない。他のアプリに制御を移す。普通は、それでいいんだけど、、DestroyWindowした瞬間というのは、ゲームスレッドの後始末が終わっていないのである。それ待ちになっているときに、DestroyWindowしてしまうもんだから、他のアプリに制御が移る。よって、ゲームスレッドの終了処理ができない(このへんのWindows側の処理には問題ありのような気がするが) だから、箱が残る。なんでやーということになるようである。
要するに、他のアプリに制御が移らなければ良いのであるからして、DestroyWindowしなければ良いのである。(えっ?あかん?) そもそも、DestroyWindowが、最小化してしまうのは、イチビッてアニメーションなんかするからである。よって、アニメーションできないように、ShowWindow(hWnd,SH_HIDE);しておけば良いのである。ウィンドゥがなければ、アニメーションだって出来やしめえ!これで、他のアプリに制御が移らない。よって、うまく終了できる、というわけである。嘘のような本当の話である。
さて、最初に書いた、移植ものにこの現象がよく見られる原因はというと、こいつらは、僕の方法と同じく、すべてゲーム処理用のスレッドを持っているからだと思われる。なんでゲームスレッドを持っているかというと、移植の手間をはぶく為、アセンブラのコードをCの命令に変換するやつをawkやperlでサクっと作って、画面や音楽まわりだけライブラリを呼び出すようにして完成〜!とかやってるからである。よーするに、Windows方式のイベントドリブン型プログラミングってやっぱゲームには向いてへんということやね。