Win32 App Window Messages - yoshimune/LearningDirectX11 GitHub Wiki

This page refers to 【Window Messages

GUIアプリケーションはユーザー・OSからのイベントに対し応答しなくてはなりません。

  • ユーザーからのイベント はプログラムとユーザーが相互作用する全ての方法を含みます。マウスクリック、キーストローク、タッチスクリーンジェスチャー、などです。
  • OSからのイベントは、プログラムの外からプログラム動作を影響させることができるものをいくつか含みます。たとえば、ユーザーが新しいハードウェアデバイスを接続した、Windowsが省電力モードに入った(スリープ・休止状態)などです。

これらのイベントは、ほとんどの場合プログラムが実行中に発生します。先の実行フローが予測できないプログラムをどのように構築すればよいでしょうか?

この問題の解決するため、Windowsは message-passing モデルを使用しています。OSはメッセ時を通してアプリケーションウィンドウと対話します。メッセージは、特定のイベントを指定する単純な数値コードです。例えば、ユーザーがマウスの左ボタンをおした場合、ウィンドウは次のようなメッセージコードを受け取ります。

#define WM_LBUTTONDOWN    0x0201

いくつかのメッセージはメッセージコードと関連するデータを持っています。例えば、WM_LBUTTONDOWNメッセージはマウスカーソルのx軸とy軸の情報を含んでいます。

メッセージをウィンドウに渡すため、OSはウィンドウに登録されているwindow procedureを呼び出します。

The Message Loop

アプリケーションは実行中に数千ものメッセージを受け取るはずです。(全てのキーストロークとマウスボタンクリックからイベントが生成されることを考慮してください。)更に、アプリケーションは個別のwindow procedureを所有したウィンドウをいくつか持つことができます。これら全てのメッセージを受け取り、適切なウィンドウプロシージャにわたすにはどうすればよいでしょうか?アプリケーションはメッセージの受け取り、適切なウィンドウプロシージャへの発送をするためにループすることが必要です。

ウィンドウを作成するスレッドごとに、OSはウィンドウメッセージのキューを作成します。このキューは、すべてのスレッドで作成されたウィンドウへのメッセージを保持します。キュー自身はプログラムからは隠蔽されています。キューを直接操作することはできません。しかし、GetMessage関数を用いてキューからメッセージを取得することはできます。

MSG msg;
GetMessage(&msg, NULL, 0, 0);

この関数はキューの先頭から最初のメッセージを除去します。もしキューが空の場合、この関数は別のメッセージが積み上げられるまでブロックします。GetMessageのブロックはプログラムを無効にしません。もし、メッセージがない場合、プログラムは何もしません。もしバックグラウンドで実行する必要がある場合は、GetMessageが別のメッセージを待機し続ける間に実行を続けるスレッドを追加で作成できます。参照:Avoiding Bottlenecks in Your Window Procedure

GetMessage最初の引数はMSG構造体のアドレスです。もし関数が正常に動作した場合、MSG構造体にメッセージからの情報が入力されます。これはターゲットウィンドウとメッセージコードを含みます。他3つの引数は、キューから受け取ったメッセージを選別するものです。大抵は0をセットします。

MSG構造体はメッセージの情報を含みますが、直接構造体を参照することは殆どありません。代わりに、別の関数に直接渡します。

TranslateMessage(&msg);
DispatchMessage(&msg);

TranslateMessage関数はキーボード入力と関係しています。キーストロークを文字に変換します。この関数が実際にはどのように動作しているのかを知る必要はありません。この関数はDispatchMessageの前に呼ばれることを覚えておいてください。

DispatchMessage関数はOSにメッセージのターゲットのウィンドウプロシージャを呼びだすように伝えます。別の言い方をすると、OSはウィンドウテーブル内のウィンドウハンドルをルックアップし、ウィンドウに関連付けられた関数ポインタを見つけ出し、その関数を呼び出します。

例えば、ユーザーがマウス左ボタンを押したとすると、以下の一連のイベントが発生します。

  1. OSがWM_LBUTTONDOWNメッセージをメッセージキューに積み上げる
  2. プログラムがGetMessage関数を呼び出す
  3. GetMessageがWM_LBUTTONDOWNメッセージをキューから呼び出し、MSG構造体を入力する
  4. プログラムがTranslateMessateDispatchMessage関数を呼び出す
  5. DispatchMessage内部で、OSがウィンドウプロシージャを呼び出す
  6. ウィンドウプロシージャはメッセージに反応するか、無視するかできる

ウィンドウプロシージャが帰ったとき、DispatchMessageに戻ってきます。これは次のメッセージのためにメッセージループにもどります。プログラムの実行時間と同じくらい、メッセージはキューに積まれ続けます。したがって、キューからメッセージを引き出し続け、発送し続けるループが必要です。以下のようなループが考えられます。

while(1)
{
    GetMessage(&msg, NULL, 0, 0);
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

このループは終了しません。これはGetMessage関数の戻り値が入る場所です。通常、GetMessageは0以外の値を返します。アプリケーションを終了するかメッセージループを抜けるときはPostQuitMessage関数を呼び出します。

PostQuitMessage(0);

PostQuitMessageはWM_QUITメッセージをキューに積み上げます。WM_QUITは特別なメッセージです。GetMessageはメッセージループの終了を知らせるために0を返します。ここでメッセージループが改定されます。

// Correct.

MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

GetMessageが0以外の値を返す限り、whileループの式はtrueと評価されます。PostQuitMessageが呼び出された後、ループ判定はfalseとなりループから開放されます。(この動作で興味深いのは、ウィンドウプロシージャは決してWM_QUITを受け取らないということです。したがって、ウィンドウプロシージャでこのステートメントを顧慮する必要はないということです。)

次の疑問は、PostQuitMessageはいつ呼び出されるかということです。この問題についてはClosing the Windowで解説します。しかし、最初にウィンドウプロシージャを記述する必要があります。

Next:【Win32 App】Writing the Window Procedure