ウィンドウ メッセージ (Win32 と C++ の概要)
GUI アプリケーションは、ユーザーおよびオペレーティング システムからのイベントに応答する必要があります。
- ユーザーからのイベント には、ユーザーがプログラム操作においてできるすべての動作 (マウス クリック、キー ストローク、タッチ スクリーン ジェスチャなど) が含まれます。
- オペレーティング システムからのイベント には、プログラムの動作に影響を与える可能性があるプログラムの「外部」のものがすべて含まれます。 たとえば、ユーザーが新しいハードウェア デバイスを接続したり、Windows の電力が低い状態 (スリープまたは休止状態) に入ったりすることがあります。
これらのイベントは、プログラムの実行中に、ほぼ任意の順序でいつでも発生する可能性があります。 実行フローを事前に予測できないプログラムをどのように構成しますか?
この問題を解決するために、Windows はメッセージパッシング モデルを使用します。 オペレーティング システムは、アプリケーション ウィンドウにメッセージを渡すことによって、アプリケーション ウィンドウと通信します。 メッセージは、特定のイベントを指定する単純な数値コードです。 たとえば、ユーザーがマウスの左ボタンを押すと、ウィンドウには次のメッセージ コードを含むメッセージが表示されます。
#define WM_LBUTTONDOWN 0x0201
一部のメッセージには、データが関連付けられています。 たとえば、WM_LBUTTONDOWN メッセージには、マウス カーソルの x 座標と y 座標が含まれます。
ウィンドウにメッセージを渡すために、オペレーティング システムは、そのウィンドウに登録されているウィンドウ プロシージャを呼び出します。 (ウィンドウ プロシージャの目的はこれです)
メッセージ ループ
アプリケーションは、実行中に何千ものメッセージを受信します。 (すべてのキーストロークとマウスボタンのクリックでメッセージが生成されることを考慮してください) さらに、アプリケーションには複数のウィンドウがあり、それぞれに独自のウィンドウ プロシージャがあります。 プログラムはこれらのメッセージをすべて受け取り、正しいウィンドウ プロシージャに配信する方法を説明します。 アプリケーションには、メッセージを取得して適切なウィンドウにディスパッチするためのループが必要です。
ウィンドウを作成するスレッドごとに、オペレーティング システムはウィンドウ メッセージのキューを作成します。 このキューには、そのスレッドで作成されたすべてのウィンドウのメッセージが保持されます。 キュー自体はプログラムに表示されません。 キューを直接操作することはできません。 ただし、GetMessage 関数を呼び出すことで、キューからメッセージをプルできます。
MSG msg;
GetMessage(&msg, NULL, 0, 0);
この関数は、キューの先頭から最初のメッセージを削除します。 キューが空の場合、関数は別のメッセージがキューに登録されるまでブロックします。 GetMessage ブロックが発生しても、プログラムが応答しなくなるわけではありません。 メッセージがない場合は、プログラムはすることはありません。 バックグラウンド処理を実行する必要がある場合は、GetMessage が別のメッセージを待機している間に実行を続ける追加のスレッドを作成できます。 (参照 ウィンドウ手順のボトルネックを回避する.)
GetMessage の最初のパラメーターは、MSG 構造体のアドレスです。 関数が成功すると、MSG 構造体にメッセージに関する情報が入力されます。 これには、ターゲット ウィンドウとメッセージ コードが含まれます。 他の 3 つのパラメーターを使用すると、キューから取得するメッセージをフィルタ処理できます。 ほとんどの場合、これらのパラメーターを 0 に設定します。
MSG 構造体にはメッセージに関する情報が含まれていますが、この構造を直接調べることはほとんどありません。 代わりに、他の 2 つの関数に直接渡します。
TranslateMessage(&msg);
DispatchMessage(&msg);
TranslateMessage 関数は、キーボード入力に関連します。 キーストローク (キーダウン、キーアップ) を文字に変換します。 この関数のしくみを実際に知る必要はありません。DispatchMessage の前にそれを呼び出すようにしてください。
DispatchMessage 関数は、メッセージのターゲットであるウィンドウのウィンドウ プロシージャを呼び出すようにオペレーティング システムに指示します。 つまり、オペレーティング システムは、ウィンドウのテーブルでウィンドウ ハンドルを検索し、ウィンドウに関連付けられている関数ポインターを検索し、関数を呼び出します。
たとえば、ユーザーがマウスの左ボタンを押すとします。 これにより、イベントのチェーンが発生します。
- オペレーティング システムは、メッセージ キューに WM_LBUTTONDOWN メッセージを配置します。
- プログラムは、GetMessage 関数を呼び出します。
- GetMessage はキューから WM_LBUTTONDOWN メッセージをプルし、 MSG 構造体に入力します。
- プログラムは、TranslateMessage および DispatchMessage 関数を呼び出します。
- DispatchMessage 内では、オペレーティング システムによってウィンドウ プロシージャが呼び出されます。
- ウィンドウ プロシージャは、メッセージに応答するか、無視することができます。
ウィンドウ プロシージャが戻ると、DispatchMessage に戻ります。 これは、次のメッセージのメッセージ ループに戻ります。 プログラムが実行されている限り、メッセージはキューに引き続き到着します。 そのため、キューからメッセージを継続的にプルしてディスパッチするループが必要です。 ループは次のように考えることができます。
// WARNING: Don't actually write your loop this way.
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) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
GetMessage が 0 以外の値を返す限り、 ループ内の式は true に評価されます。 PostQuitMessage を呼び出すと、式は false になり、プログラムはループから抜け出します。 (この動作の興味深い結果の 1 つは、ウィンドウ プロシージャが WM_QUIT メッセージを受け取らないということです。そのため、このメッセージのケース ステートメントをウィンドウ プロシージャに含める必要はありません)。
次の明らかな疑問は、PostQuitMessage を呼び出すタイミングです。 「ウィンドウの作成」のトピックでこの疑問に戻りますが、最初にウィンドウ プロシージャを記述する必要があります。
投稿メッセージと送信済みメッセージ
前のセクションでは、キューに入るメッセージについて説明しました。 場合によっては、オペレーティング システムがウィンドウ プロシージャを直接呼び出し、キューをバイパスします。
この区別の用語は、混乱を招く可能性があります。
- メッセージの投稿 は、メッセージがメッセージ キューに入り、メッセージ ループ (GetMessage および DispatchMessage) を介してディスパッチされたことを意味します。
- メッセージの送信 はキューをスキップするということで、オペレーティング システムはウィンドウ プロシージャを直接呼び出します。
現時点では、違いはあまり重要ではありません。 ウィンドウ プロシージャは、すべてのメッセージを処理します。 ただし、一部のメッセージはキューをバイパスし、ウィンドウ プロシージャに直接移動します。 ただし、アプリケーションがウィンドウ間で通信する場合は、違いが生じます。 この問題の詳細については、トピックの「 メッセージとメッセージ キューについて」を参照してください。