ウィンドウ メッセージ (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の前にそれを呼び出してください。 興味がある場合は、MSDN ドキュメントへのリンクから詳細情報が提供されます。

DispatchMessage 関数は、メッセージのターゲットであるウィンドウのウィンドウ プロシージャを呼び出すようにオペレーティング システムに指示します。 つまり、オペレーティング システムは、ウィンドウのテーブルでウィンドウ ハンドルを検索し、ウィンドウに関連付けられている関数ポインターを検索し、関数を呼び出します。

たとえば、ユーザーがマウスの左ボタンを押すとします。 これにより、一連のイベントが発生します。

  1. オペレーティング システムは、メッセージ キューに WM_LBUTTONDOWN メッセージを配置します。
  2. プログラムは GetMessage 関数を呼び出します。
  3. GetMessage はキューからWM_LBUTTONDOWN メッセージをプルし、 MSG 構造体を入力します。
  4. プログラムは TranslateMessage 関数と DispatchMessage 関数を呼び出します。
  5. DispatchMessage 内では、オペレーティング システムによってウィンドウ プロシージャが呼び出されます。
  6. ウィンドウ プロシージャは、メッセージに応答するか、無視することができます。

ウィンドウ プロシージャが戻ると、 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 以外の値を返す限り、while ループの式は true に評価されます。 PostQuitMessage を呼び出すと、式は false になり、プログラムはループから抜け出します。 (この動作の興味深い結果の 1 つは、ウィンドウ プロシージャが WM_QUIT メッセージを受け取らないということです。したがって、このメッセージの case ステートメントをウィンドウ プロシージャに含める必要はありません)。

次に明らかな質問は、 PostQuitMessage を呼び出すタイミングです。 トピック「 ウィンドウを閉じる」でこの質問に戻りますが、最初にウィンドウ プロシージャを記述する必要があります。

投稿されたメッセージと送信されたメッセージ

前のセクションでは、キューに送信されるメッセージについて説明しました。 場合によっては、オペレーティング システムがウィンドウ プロシージャを直接呼び出し、キューをバイパスします。

この区別の用語は、混乱を招く可能性があります。

  • メッセージを投稿すると、メッセージはメッセージ キューに入り、メッセージ ループ (GetMessage と DispatchMessage) を介してディスパッチされます。
  • メッセージを送信すると、メッセージはキューをスキップし、オペレーティング システムはウィンドウ プロシージャを直接呼び出します。

現時点では、違いはあまり重要ではありません。 ウィンドウ プロシージャは、すべてのメッセージを処理します。 ただし、一部のメッセージはキューをバイパスし、ウィンドウ プロシージャに直接移動します。 ただし、アプリケーションがウィンドウ間で通信する場合は、違いが生じます。 この問題の詳細については、「 メッセージとメッセージ キューについて」を参照してください。

次へ

ウィンドウ プロシージャの記述