少しずつ佳境に入ってきました。ウィンドウプロシージャはDOS形式のプログラムには見られない、ウィンドウズプログラミングらしい面白い部分です。この辺がよく分かると、かなり見通しが良くなるような気がします。
|
////////////////////////////////////////////////////////////////////
// メッセージループです。
int Run()
{
MSG stMsg;
// メッセージを送ります。
while( GetMessage( &stMsg, NULL, 0, 0 ) )
{
TranslateMessage( &stMsg );
DispatchMessage( &stMsg );
}
return stMsg.wParam;
}
|
メッセージの取得
WinMain()で最後に呼ばれる関数、それがこのRun()です。この関数では、メッセージの転送と取得を行います。
メッセージとは、ウィンドウズから送られてくる正の整数値です。例えば「最小化しろ」とか「左クリックされたぞ」など、こういったイベントが起きた時に送られてきます。これらのメッセージがどういう値なのかはWINUSER.Hに書かれています(q:\devstudio\vc\INCLUDE\の方)。
GetMessage()というAPIは、このメッセージキューからメッセージを取り出します。そして、WM_QUIT、つまりアプリケーション終了の知らせが送られてきた時に、戻り値が0になります。そうすれば、この関数が終了し、WinMain()も終了し、アプリケーションが終了するというわけです。 |
キーコードの変換
次に、メッセージをTranslateMessage()というAPIに掛けます。この関数は、キー入力関係のメッセージの中で、仮想キーコードを使用するWM_KEYDOWNとWM_KEYUPが送られてきた時、通常のキャラクターコードを使用するWM_CHARに変換してくれます。 まぁ、そんなに気にすることはないでしょう。 |
ウィンドウプロシージャへの送信
この関数の詰め、DispatchMessage()です。このAPIはウィンドウプロシージャと呼ばれる関数へと、メッセージを送ります。 では、ウィンドウプロシージャとは何なのでしょうか。
ウィンドウプロシージャとは、メッセージを処理するための関数のことです。すべてのウィンドウはウィンドウプロシージャを持ち、送られてくるメッセージを処理します。 |
LRESULT CALLBACK WndProc( HWND p_hWnd, UINT p_uiMsg, WPARAM wParam, LPARAM lParam )
{
// 略。次回を待て。
}
こちらで用意すると言っても、好き勝手な関数を使えるわけではありません。関数の形はWindowProc()という関数のページを読んでください。引数と戻り値の型さえ合っていれば、関数や引数の名前は違っていても構いません。
ウィンドウプロシージャは、ウィンドウクラスを登録するときに指定します。ウィンドウクラスを登録するときにWNDCLASS型の構造体へのポインタを渡しますが、このメンバlpfnWndProcに、作製したウィンドウプロシージャ関数へのポインタを渡します。コードでは次のようになっています。 |
BOOL InitApplication( HINSTANCE p_hInstance )
{
WNDCLASS stWndClass; //ウィンドウクラスです。
stWndClass.lpfnWndProc = WndProc;
// 他のメンバは略。
// ウィンドウクラスを登録します。
if( !RegisterClass( &stWndClass ) )
return FALSE;
return TRUE;
}
このようにして、「どのウィンドウがどのウィンドウプロシージャで処理されるのか」ということを指定しておくわけです。
|
「ウィンドウ」の意味
ウィンドウズの特徴のひとつは、オブジェクトのほとんどがウィンドウであり、これらの様々なウィンドウが集まってアプリケーションを作っているということでしょう。OSのシステムそのものとして、「ウィンドウ」という存在があるわけです。 これらウィンドウには、ウィンドウプロシージャが存在し、送られてくるメッセージを逐一処理します。この部分は、目に見えません。普通にアプリケーションを使っていると、ウィンドウズの奥の方でまとめてやっているような気がしますが、実際にはアプリケーションが持つウィンドウプロシージャで処理しているわけです。
本当のところ、ウィンドウには「表示される矩形の単位」というよりは、「メッセージを処理する単位」という意味の方が強いでしょう。
ウィンドウズはイベントドリブン(Event driven)なOSと言われます。つまり、「キーが押された」だの「マウスが動かされた」だのといった様々なイベントが起き、そこで初めてアプリケーションが動くという形を取っているわけです。
主要アプリケーションのほとんどは「何らかの動作を実行している時間よりも、ユーザーからの入力を待っている時間の方が長い」と言われます。これに当てはまらないのは、非常に美麗な3Dアニメーションを秒間60コマで再生するようなものくらいでしょう(これが格闘ゲームとかなら当てはまるかも)。 |
MFCの場合
さて、MFCの場合ですが、なんかかなりやっかいなことをしているみたいです。
まずRun()ですが、この関数はCWinThreadのメンバ関数Run()をまねして作製しました。この関数は、ちゃんとAfxWinMain()の最後の方で呼び出されています。
で、問題はウィンドウプロシージャの指定なんです。先に説明したとおり、ウィンドウプロシージャの指定はウィンドウクラスを登録するときに行います。ところが、MFCの中で登録される場合にはDefWindowProc()という関数へのポインタを指定しているのです。
サブクラス化とは、ウィンドウプロシージャを入れ替えてしまうことです。この機能を使うと、送られてきたメッセージを横取りすることができます。実際にはSetWindowLong()というAPIを使用します。この関数で、ウィンドウプロシージャを入れ替えることができるのです。
実際にSetWindowLong()が呼び出されているのは_AfxCbtFilterHook()という関数です。この関数はフックプロシージャと呼ばれるもので、まぁウィンドウプロシージャとは兄弟姉妹のような関係でしょうか。
このフックプロシージャはスレッドローカルなフック(ローカルフック)としてインストールされます。これで、このアプリケーションに送られてくるメッセージが、まずこのフックプロシージャに送られてきます。といっても、フックのタイプがCBTフックというもので、これはウィンドウが構築されたり削除されたり移動したりフォーカスが移ったりしたときに送られるメッセージのみ、フックプロシージャに送ってもらうというものです。
では、このフックがどこでインストールされているかというと、AfxHookWindowCreate()という関数です。で、この関数をたどってみるとAfxHookWindowCreate()<CWnd::CreateEx()<CWnd::Create()<CFrameWnd::LoadFrame()となります。
では、その置き換えられたウィンドウプロシージャがなんなのかというと、AfxGetAfxWndProc()という関数となっています。が、この関数はAfxWndProc()という関数へのポインタを呼び出すだけです。 |
次回予告
MFCにおけるウィンドウプロシージャは、サブクラス化とフックのコンビネーションブローという見事なオチが着きました。もう笑うしかないですね。どーしてこんなことしてるのかは私には分かりません。誰か教えてください……。
次回はウィンドウプロシージャの中身を見てみることにします。これがまた、MFCではかなり色々やっているみたいですが、まぁクラスウィザードを使って、メッセージを関数に関連づけてくれているわけですから、目頭立てるものじゃぁないのかもしれませんね。 |
(C)KAB-studio 1997 ALL RIGHTS RESERVED. |