第50回 The Palette Manager(その1) 99/6/9

パレット動作は複雑である。DirectDrawで描画と言えども、ウィンドゥモードで動作させる限りは、パレットマネージャからの束縛から逃れることは出来ない。ウィンドゥモードでの描画がうまくいきません〜という人は、必ずと言っていいほど、このパレットマネージャの動作についてまったく理解していない。

まあ、かく言うやねうらおも、その一人であった。なるべくなら、そんな気持ち悪いパレットの世界から抜け出して、65536色以上としてゲームを作ったほうが、いくら楽かわからないし、いまや6万円そこいらのマシンでさえ、そうするのに十分なスペックを持っている。(だって、PentiumII 450MHzが2万で売られてる御時世だもんね!)

しかし、ここ最近でハードは劇的な進化を遂げた。ユーザーのマジョリティ(大多数)は、まだPentium166MHz前後であると考えるべきであろう。「パレット?ああ、そんなん必死こいて勉強してるバカいたなー」と笑って済まされる時代が早く来て欲しい。もうそういう時代になりつつある。

今回の教則本は、速水祐の「Win32スーパーテクニック」である。有名なZOB Stationの管理者である。Windowsのパレットってなんでこんなややこしんじゃー!!と思っていた矢先だったので、衝動買い(¥5,800+税)してしまったが、たいしたことは書いてなかった。ちゅーか、この人も、パレットまわりをいろいろ誤解している。これならば、MSDNのオンラインマニュアル(Visual C++6.0についてるもの。以下、オンラインマニュアルというのは、すべてそれを指す)でThe Palette Managerの項を読んだほうがまだマシな気はする。(英文で32ページもあるけど...)

ちょっと一例を書くと、この本の283ページには、

最も近い色とは、

(R-R')^2 + (G-G')^2 + (B-B')^2

の値が最も小さなデータのことのようである。

とあるが、こんなものは大嘘である。それは、Windowsのパレットマネージャが色の近さを、単に幾何学的距離(=Euclidean distance)だと仮定したアルゴリズムを採用しているに過ぎない。まあ、この文章の文脈からすると、パレットマネージャが「最も近い色」にマッピングするときの「最も近い色」の定義について書いているつもりなのかも知れないが、この書き方では、そうは読めない。

こんなつまらない突っ込みを入れても仕方がないのだが、この最適パレット選択ルーチンをいつ自分で実装しなければならないかわからないだけに、結構重要である。それというのも、CreateDIBSectionSetDIBsToDeviceの使えない状況というのは、少なからず存在するからである。この二つの関数は、Win32のビットマップ操作のなかで最重要な関数だと思うのだが、後者はオンラインマニュアルではいまだ日本語化すらされておらず、これはマイクロソフトの陰謀ではないかと思っている。だから、SetDIBsToDeviceって知らん人が多いんでないかなー。Bitmapの読み込みなんて、(ビットマップの)インフォヘッダーとピクセルへのポインタだけあったら、あとは、サーフェースをGetDCして、SetDIBsToDeviceで一発転送なのに、わざわざCreateDIBSection作って、そいつにちまちまCopyMemoryして、そいつをbltしている人も少なくない。こんなことは、まったく無駄である。ついでに言えば、Win98以降であれば、こいつはJPEG読み込みにも対応しているのである。そういや、パレット関連の最重要なメッセージWM_PALETTECHANGED、WM_QUERYNEWPALETTEも、日本語化されとらん!おまけに英文で書いてある内容も非常にトートロジカル(同意語反復)で、何を言ってんのかさっぱりわからん!文章は翻訳できるのに、意味がわからん。そんなのってあるか?この場をお借りして、ちょっと言わせてもらえれば...おんどりゃ、日本なめとんか!!ゆうちゃなんやけどな、日本はモノカルチャルソサエティなんや!日本語に誇り持っとんのや。ちゅーか、日本人は日本語しか読まへんのや!よお覚えとけ!(遠吠え)


第51回 WM_PALETTECHANGEDで風邪を引く(なげやり) 99/6/11

PALETTEについていろいろ試していたら、風邪を引いた。この間に、200ページ分ぐらい英文を読んだような気もする。(やり慣れんことするから知恵熱出たんちゃうんか?>俺)

そういや、第4E回で書いたSetPaletteのバグだが、DirectXの日本語(online)マニュアルには、

同じパレット、同じサーフェスに対し、 IDirectDrawSurface2::SetPaletteが何度か続けて呼び出された場合、パレットの参照カウントは一度しかインクリメントされない。連続する呼び出しは、パレットの参照カウントに影響を与えないのである。

と書いてある。そしたら、あのバグは何だったんだ。おかしいじゃないか。

まあ、それはそれとしてやねー、ウィンドゥモードでDirectDrawってのは、意味があることなんかなーと思うわけよ。そもそも、遅いんよ。場合によっては、適合パレット(転送元ビットマップと転送先ビットマップのパレットを同じにすること。これをWindowsが認識すると、パレット変換のオーバーヘッドが無くなるので、数倍速くなる)で転送してんのとほとんど変わらへんのちゃうかなーと思わんでもない。そもそも、DirectDrawが優位なケースというのは、ビデオメモリ間の転送をハードウェアのアクセラレーションで行なえる場合であって、そうでない場合は、使う意味がない。

だいたい、サーフェースのLockは、重いのである。(Win16Mutexを呼び出しているという噂もある) GetDCも潜在的にLockを呼び出すから、同様に遅い。こんなことなら、サーフェースをぐりぐりいじくり倒す場合などなら、自分で必要分だけメモリを確保して、そこで描画を行ない、ラスター単位でCopyMemoryでもしたほうが良いような気がする。

まあ、そんな泣き言を言っていても始まらないので、DirectDrawPaletteについて考えていく。こいつを使う場合でも、ウィンドゥモードでは、他の窓との協調動作が問われるわけであって、Windowsのパレットシステムの理解を避けては通れない。これが、非常にうっとーしいのである。まずは、パレットを変更するタイミングに送られてくるメッセージから調べていくことにしよう。一番重要なパレットメッセージとは、言わずと知れたWM_PALETTECHANGEDである。

WM_QUERYNEWPALETTE
The WM_QUERYNEWPALETTE message informs a window that it is about to receive the keyboard focus, giving the window the opportunity to realize its logical palette when it receives the focus.

このメッセージは、入力フォーカスを受け取る直前に届き、入力フォーカスを受け取った時に論理パレットをリアライズするための機会を与える。

Return Values
If the window realizes its logical palette, it must return TRUE; otherwise, it must return FALSE.

論理パレットをリアライズしたならtrue,そうでなければfalseを返しなさい。

しかしやねー、この説明では、何をどうやっていいのかちっともわからん!WM_PALETTECHANGEDも見なさいと書いてあるので、そちらも見る。

WM_PALETTECHANGED
The WM_PALETTECHANGED message is sent to all top-level and overlapped windows after the window with the keyboard focus has realized its logical palette, thereby changing the system palette. This message enables a window that uses a color palette but does not have the keyboard focus to realize its logical palette and update its client area.

このメッセージは、入力フォーカスを持つウィンドゥがその論理パレットをリアライズし、それによってシステムパレットが変更された場合に、すべてのトップレベルウィンドゥとオーバーラップウィンドゥに送られる。このメッセージにより、カラーパレットを使うが、入力フォーカスを持たないウィンドウがその論理パレットをリアライズし、クライアント領域の更新できる。

えっ?入力フォーカスが無いとパレットをリアライズする権利はないのんちゃうんけ? 入力フォーカス無くても、リアライズしてええんけ?

Remarks
This message must be sent to all top-level and overlapped windows, including the one that changed the system palette. If any child windows use a color palette, this message must be passed on to them as well.

メモ
このメッセージは、パレットを変更したウィンドゥも含めて、すべてのトップレベルウィンドゥとオーバーラップウィンドゥに送られる。子ウィンドゥが、カラーパレットを使うならば、そいつらにもこのメッセージを通知してやりなさい。

To avoid creating an infinite loop, a window that receives this message must not realize its palette, unless it determines that wParam does not contain its own window handle.

無限ループにならないように、このメッセージを受け取ってもwParamが自分のwindow handleでないことを確認しない限り、パレットをリアライズしてはならない。

ここで発生する「無限ループ」ってのは、WM_PALETTECHANGEDを受け取って、パレットをリアライズする、その結果、またもやWM_PALETTECHANGEDが発生するという意味の無限ループのことを指すようである。

ところがやねー、WM_PALETTECHANGEDでパレットリアライズしたら、他のウィンドゥにWM_PALETTECHANGEDの通知が行って、そいつがまた、パレットリアライズしたら、そこでまたWM_PALETTECHANGEDが発生するんとちゃうのん? WM_PALETTECHANGEDでパレットをリアライズする窓が2つ開いとったら、収拾つかんよーなるんでないの?この文章を読む限り、わけがわからなくなる。そう。どこかに落とし穴があるのだ!


第52回 CRITICAL SECTION(そんなんもあった) 99/6/13

第4C回でMutexは遅いと書いたが、やっぱり遅かった。同一プロセス内で同期をとるならば、CRITICAL_SECTIONを使うべきであって、Mutexなんかで同期をとってはいかんのである。そんなわけで、ゲームスレッドを実現しているライブラリがあるので、紹介する。以前、一度ここでも書いたことがある、ぷよーんさんのページ

http://puyon.pns.to/

にある、GPL(Game Programming Library)がそれである。(しかし、さりげなくトンガドメインなのねー。やってくれるなぁ...)

やねうらお的には、このソース、速度面ではいろいろ気になる部分もあるのだけど、まあ、参考にするにはちょうどいいんでないかなー。エラー処理もしっかりしてるし。(あたしゃ、こんな奇麗なソースは、よー書きましぇん)

さて、PALETTEの話の続きなのだが、実は、WM_QUERYNEWPALETTEの「論理パレットのリアライズ」とWM_PALETTECHANGEDの「パレットのリアライズ」とでは意味が違うのである。パレットというのは、共有リソースであって、256色あるうち、Windowモードでは前と後ろの10個ずつをWindowsがSTATICパレットとして予約している。(変更できるが、変更したときの動作は保証外である )

よって、236色はユーザーに自由に割り振れる。これは、トップウィンドゥに優先権がある。フォアグラウンドウィンドゥでShowWindowが実行されると、WM_QUERYNEWPALETTEが発生する。このメッセージを受け取ると、自分の使うパレットを予約ないし登録できる。つまり、フォアグラウンドウィンドゥはパレット設定に関して、優先権があることになる。(あまり書籍では正しく語られていないが)その後、236色が余っているならば、次のウィンドゥにWM_QUERYNEWPALETTEを発行し、そのウィンドゥが使用するパレットを予約ないし登録させる。この動作は、236色すべて埋まるか、ウィンドゥが無くなるまで続けられる。これが終わったあと(とは限らないが)、すべてのウィンドゥに対して、今度は、「パレット設定されたから、使ってちょー」というメッセージWM_PALETTECHANGEDが届くのである。よって、バックグラウンドで動作するウィンドゥでは、新たに設定されたパレットを使用して、クライアント領域を再描画することによって、論理パレットにより忠実な描画が可能となるわけなのだ。(実際は、この説明は正しくない。WM_PALETTECHANGEDのメッセージは、WM_QUERYNEWPALETTEの発行された回数だけタイムラグつきで発生する)

つまり、WM_PALETTECHANGEDでは、パレットの登録作業は行なわない。それにもかかわらず、書籍・マニュアル等には、「パレットのリアライズを行なうとある」この混乱は、RealizePaletteから来ているのだと思う。こいつは、バックグラウンドで動作しているときは、自分の論理パレットを予約・登録するのではなく、いま登録されているパレットにマップする(関連付ける)ように使う。このような二義的なRealizePalette関数の動作が、非常に多くの誤解と混乱を生じさせている原因なのだと思うが、このメカニズムを考え出した人は、意外にセンスがあるんでないかなーと感じる。

そんなわけで、DirectDrawを使用する場合でさえ、ウィンドゥモードで動作させるのならば、 WM_QUERYNEWPALETTEに対して使用するパレットの予約を行なわないと、「次にフォアグラウンドにあるウィンドゥ」にパレットの予約・登録権を譲渡してしまうことになる。使用する寸前になってから、DirectDrawPaletteを使うと、パレットを大幅に「次にフォアグラウンドにあるウィンドゥ」から奪うことになり、当然、論理パレットの再マッピングが始まる。これは、速度低下の原因となる。ウィンドゥモードでパレットアニメーションなんかやるぐらいならば、65536色でソフトウェアで65556Wordのパレットテーブルを持ってアセンブラキチキチに使って転送してやるほうが、よっぽど高速であるという話もある。ただし、その際にクリップ処理が必要になってくるので、結局のところDirectDrawClipperを仕掛けてbltで転送しなくてはならない。あるいは、自前で更新リージョンを見ながら転送するか、である。パワープログラマは、そうするのかも知れないけど、こんな転送ルーチンを作らされた日にゃ、何のためのDirectDrawだかわかんなくなる。

そして、なお悪いことに、パレットは、あらかじめ予約しておき、そのパレットをSetEntryを使って、予約しておいた部分だけを部分的に変更しているのに、パレットのリマッピングは生じるのである!どうゆーこっちゃねーん!!めっちゃ遅いやんけー!!IE4.0が悪いだとか、他のアプリの WM_PALETTECHANGEDの処理の仕方がまずいだとか、いろいろ原因はあると思うが、ウィンドゥモードで256色パレットというのは現実的な選択ではないのである。

パレットの問題は、実は、これくらいでは収まらないのである。本当の地獄は、ここからなのだ!!


第53回 パレット地獄(俺の青春を返せ〜) 99/6/14

どうも、SetPaletteは案外、外道であることがわかってきた。こいつのために週末がつぶれた。ひょっとすると、まだしばらく潰れるかも知れない。どれが自分の犯しているバグで、どれがDirectX側のバグ(ないしは仕様)なのか判然としかねる部分が多々ある。まあ、Palette関連のソースで参考になるものと言えば、DirectXSDKに付いてくるfox bearとpaletteなのであるが、前者のソースを眺めていてひっくり返った!

case WM_QUERYNEWPALETTE:
//
// we are getting the palette focus, select our palette
//
if (!bFullscreen && lpPalette && lpFrontBuffer)

そうである。WM_QUERYNEWPALETTEに応答してSetPaletteする部分で、フルスクリーンモードではそのタイミングではSetPaletteしていないのである。これは、第4E回で僕が書いたバグ対策だと思われる。こんなことマニュアルのどこにも書いてへんやんー。もーかなわんなー。ついでに言えば、CreatePaletteして、SetPaletteでサーフェースにアタッチした場合、最後にはサーフェースにSetPalette(NULL);として、サーフェースからデタッチした後に、lpPalette->Release();とパレットオブジェクトを解放しないといけない。おまけに、サーフェースをロストしている場合、lpSurface->Restore();を呼び出してからパレットをデタッチするということも怠ってはならない。確実なアプリを書くためには、このぐらいの終了処理はもはや常識なのである。(そのわりには、ろくに書籍では説明されていないし、終了処理をはしょっているものすらある。何、考えてんじゃ)

しかしやねー、他人の作ったプログラム(ここではDirectXね)に対してインターフェースを構築するときって、なんかいっつも試行錯誤を伴うもんやねー。それも、たいてい、無駄としか言いようがないような試行錯誤だったりするわけよ。何時間も悩んで、納期迫ってるし、やばいなーとか時計と睨めっこしながら、胃をキリキリさせて、あーやっぱしこんな仕事引き受けるんやなかったなーとか葛藤しながら、そんなことゆうてても始まらへんという思考の循環を幾度となく繰り返していると、俺ってなんて無脳なんや!と思えてくるから不思議だ。他人のプログラムにほとんど依存しないプログラムならば開発スケジュール通りに事が運ぶのに、他人のプログラムが噛んでくるともう駄目なのだ。あー、いままで生きてて楽しいことってあんましなかったよなーとか幼稚園の遠足のとき佳代ちゃんと一緒に弁当食べたっけーなーあんときゃタコのウィンナー僕に食べさせてくれたもんなーあんときがいままで生きてきたなかで一番幸せだったよなーとか次に生まれてくるときは、空を自由に飛べる鯉のぼりに生まれてきたいなぁとかつらつら考えるに...いや、そんなこと考えとらんと早よ仕事せえ!ごもっとも。ごもっともでごじゃるよ。ニンニン。


第54回 ゲイツ君日本語を勉強したまえ!(日本語は美しい) 99/6/16

日本語はあいまいだから論理的な推論は難しくそれゆえ哲学の育つ土壌が無かったのだと言った馬鹿教授が居たが、そんなことはない。日本語であろうと、関西弁であろうと論理的な推論は可能である。この馬鹿教授は、論理学を基礎から勉強しなおしたほーがええんでないのとか思わんでもない。哲学が育たなかったのは、そんなこととはまったく別の風土的な問題である。

かく言う僕は、日本語は美しいと思っている。美しい日本語がある、と言うのではない。(この違い、わかってもらえんかなー) そもそも美しい日本語とはなんや?と問われると答えに窮してしまうのだが、公準化された標準語が美しい日本語であるとは思わない。思ったことすらない。そういうしとたちに文学を語る資格はないのである。いや、文学はどうでもいいんだよ、今回はね。

そんなことを言うのもやねー、TextOutが問題なのである。ちょっとメッセージ表示のためにバックバッファをGetDCして、TextOutで文字を表示させていたのだが、こいつがすこぶる遅いのである。あまりの遅さに、当初GetDCがリニアなポインタを用意するのに時間がかかっているのだと信じてしまっていたのだが、根本的な遅さの原因は、TextOutであった。しかも、こいつ、日本語だけ遅いんでやんの。TrueTypeFontの展開に時間が掛かっているのか、なんだか知らねえけんどもよー、そりゃあんまりでないのー。

そういや、unicodeでも漢字の扱いがひどい。中国のGBコード、台湾のBig5、TCA-CNS、韓国のKS C、日本のJIS Xの共通する文字は一つのコードにしてしまうのである。(CJK統合漢字と言う) そんなことしたら、文字順序だって一致しないからどでかい変換テーブルが必要なのである。さらに悪いことに、異なる漢字体でも似た字形のものは、1つのコードが強引に割り当てられている。おんどりゃーーー!!日本語、ナメとんのかー!!国際化対応はわかったから、日本語めちゃめちゃにするんだけはやめてくれー!!である。

Windows2000で国際化対応はええけど、日本語はちゃんと正しく出んねやろな?「黄のカレー」が「糞のカレー」とかなってたら、しまいにゃテポドンでいてまわれんで。(^^;


第55回 迷えるプログラマー達よ!コンポーネントするのです!(ActiveX) 99/6/18

きっかけは、LoadLibraryであった。DirectXがインストールされていない環境でエラーを出すために、実行時にDirectXのインターフェースを照会していた。そのために、LoadLibraryを使用していたのだが、こいつを使うとFreeLibraryしたのちに、DLLの存在していたメモリ内でエラーが出る。

おそらく、参照カウントまわりの処理がおかしいのだと思って、ぷよーんさんのGPLを見ると、なぜか、m_pDirectDrawをRelease()させていない。(こうすればエラーは出ないのだが) 不思議に思って、掲示板で質問してみたところ、以下のような回答をいただいた。

>LoadLibraryを使ってddraw.dllを読み込んだ場合、
>m_pDirectDrawをRelease()しようとしたりするとFreeLibraryした
>あとでDLLの存在していたメモリ内でエラーが発生します。

LoadLibraryを使うと、参照カウンタ自体の意味が無くなります。
COMに正式に対応させた書き方は、

::CoInitialize(NULL);
::CoCreateInstance(CLSID_DirectDraw,NULL, CLSCTX_ALL, IID_IDirectDraw, (LPVOID*)&m_pDirectDraw);
if(m_pDirectDraw)
IDirectDraw_Initialize(m_pDirectDraw, NULL);

if(m_pDirectDraw)
m_pDirectDraw->Release();

となります。
これだとCOMのカウンタによって、自動的にDLLが解放されます。
実はCOMをLoadLibraryで使うこと自体が間違いの原因です。
以前に直そうと思ってそのままになっていました。
GPLではこっちの方式に変更する予定です。

COMは一意的な名前によって、DLLの名前に依存せずに使えるので、
LoadLibraryを使うのは、本当はかなり無駄です。

そんなわけで、買ったまま眠っていた吉田弘一郎の「ActiveX教養講座」を読むことにした。こいつの本は、「MFCライブラリの使い方」、「極めるVisualC++」という本(すべて技術評論社)から出ているのだが、その二冊がどちらかと言えばカスな本で、御本人が試行錯誤を繰り返しながら書いてるもんで、読んでいて眠たくなる。あのなー、おっさん。金とんなら、もっとしっかり書け!と読者の一人として言わせてもらおう。

ところが、この「ActiveX教養講座」のほうは、内容は読みやすいし、しかも面白い。挿し絵が笑える。しかし、タイプミス(誤字などではない)が、数十個所あるのはなんでなんかなー。こんなタイプミスだらけの本は、見たことがない。(ひょっとして文字化けか?)

ともかく、ActiveXは一応理解はしたが、先の問題に対する回答らしきものはわからなかった。COMオブジェクトをLoadLibraryして使うのが間違いならば、DirectXにあるようなDirectDrawCreateとかで、インターフェースを照会するのはええのんか?あれは、内部的にはどないなっとんのや?

なんか考えるだけ無駄のような気がしないでもないので、今日のところは勘弁しといてやろう。というか、もー勘弁して頂戴!


第56回 ウィンドゥのサブクラス化について(CMsgHookなんか作ったりした) 99/6/19

仕事がおしてるから、今回は、短刀直入だぞ。

ウィンドゥのサブクラス化を行なうためには、一番最後に派生したウィンドゥクラスから継承するのが簡単なのたが、それを一般的に行なう手段がC++には用意されていない。つまり、ウィンドゥをクラスと一対一に対応付けること自体が、C++の枠組みにおいては不自然であると言っても過言ではない。

そもそも、継承と言うのは、オブジェクト指向が破綻する原因であるような気がしてならない。ActiveXにおいては、継承というのは、通常、IUnknownからしか継承しないし、意図的に継承を避けるメカニズムを採用したと言える。これは、C++の継承の脆弱さを補う必要があったため、継承のメカニズムそのものを別の仕掛けで提供しようとしたからである。

そんなわけで、サブクラス化は、メッセージをフックしていけばよろしい。とか言って、昔のMSJ(マイクロソフトジャーナル)を見てたら、No.50号にそれと同じ趣旨の記事が載ってる。ちゅーても、1997年なもんで、もう書店には無いだろう。最近、『Windows2000プログラミングワークショップ』という、MSJのWindows関連のおいしいところだけひとまとめにした、広辞苑のような本が出版された。8000円+税である。しかし、これを読めば何がわかる、という類の本ではない。(あかんやん)

と言うのも、もともと、MSJは読み物の集合体であって、週刊誌のような感覚で読むのだが、それがこの厚み分だけあると、げそっとする。まあ、退屈しのぎにはちょうどいいんでないかな。

そんなわけで、開発中のゲームプログラミング用のSDK(yaneSDK)も、C++らしくすべて書き直しをすることを決意。なんか、せっかく動いていたものを潰すんだから、非生産的な気もしないでもないし、自己満足のためにやっているような気もしないでもない。ともかく、yaneSDKの公開まであと少し。もう、ちっと待って!


第57回 遊びじゃないのよプログラミングは!(CMsgHookの仕組み) 99/6/22

なんか、C++風に完全にリライトすることを決意したのが、金曜日の晩だったのだが、土曜に打ち合わせを挟んで、もはや月曜の明け方までかかるとは思いもよらなかった。ソースは全面改訂。

機嫌良くプログラムして、3日ぶりにコンパイル(コンパイルなんて、ちっともしとらんかったのよ)してみると、

test.exe - エラー 301、警告 10

ほえほえー。前途多難。

おまけに、今回、MIDIは対応させなくて良いことになり(CD-DAで再生するんだと)、結局、他人に使ってもらえるライブラリなのかすこぶる疑問。

そんなわけで、ここ数日間、まったく生産的なことをしていないのではないかと思わないでもない。しかし、自分でも不思議なのだが、ひとたび一つのことが気になり始めると、それをどうにかしないことには次のことが出来ないという典型的なシングルタスクなプログラマなのである。街角でやねうらおを見かけたら、「Windows3.1」と後ろ指を指されるのは覚悟している。

さて、メッセージフックの続きだが、普通、Windowを表示するクラスを用意する。仮にCWindowとしよう。そこにDirectDrawを制御するクラスCDirectDrawを用意する。

まず、メッセージ処理用の関数は、CWindowの中に用意すべきだが、メッセージ処理を行なうCallback関数としてCWindow::WinProcを指定することは出来ない。C++に精通している人なら言うまでもないことだが、クラスのなかの関数はthisポインタを伴って呼び出されるから、関数ポインタとして指定できないのである。(staticな関数はthisを伴わないから呼び出せる)

((CWindow*)0)->CWindowとやって、thisからのオフセットアドレスを取得しておき、thisとオフセットアドレスをセットで管理することでクラスの非staticなメンバ関数を呼び出そうかと思ったのだが、どうやら最近のコンパイラは型チェックが厳しく、そんなことは出来ないのである。

CDirectDraw::WinProcを呼び出すどころか、CWindow::WinProcを呼び出すところでつっかえてしまうのである。そうなると、ウィンドゥクラスのカプセル化って、どうやって行なうねん?って疑問が生じる。解決策としては、

staticメンバ関数(なしいはグローバル関数)を経由して、そこから、グローバルなクラスのインスタンスを利用してCWindow::WinProcを呼び出す

まー、現実的な解決策だろうが、普通にやるとちっともclassらしくないし、このあと拡張に困りそうである。

そんなわけで、前回書いた、CMsgHookの登場である。手品の種は、何のことはない。こいつをCWindowやCDirectDrawの基底クラスに置くのである。そして、CMsgHookのなかでvirtualなWinProcをメンバ関数として用意する。CMsgHookは、HWNDとCMsgHook*とを対応づけるグローバルな連想記憶(=たとえば、staticなCMapPtrToPtr)を持っていて、HWNDからCMsgHook*を得て、そいつのWinProcを数珠つなぎに呼び出す。そうすると、CMsgHookの派生クラスはめでたくメッセージをHookできるということである。

しかしやねー、なんか基底クラスに置くのは気持ち悪いという気がしなくもない。やねうらおは、クラスの派生というのが嫌いなのである。どうも、C++的なオブジェクト指向の一番醜い部分のような気がしてならない。派生派生派生と3回となえると、早よせえ!に聞こえなくもないところも嫌である。(妄想)


戻る