前回説明したCreateWindowEx() 関数で生成したウィンドウを機能させるには、ウィンドウに対して行われる操作をプログラムで処理する必要があります。ウィンドウに対する操作は、メッセージと呼ばれる要求で届けられるため、プログラムではメッセージを解析して適切に処理しなければなりません。

コマンドラインでは常にプログラム主導で流れが決定しましたが、Windows アプリケーションでは、ウィンドウがメッセージを受け取るのを待機し、メッセージによって実行するプログラムを決定します。

マウスクリックやキー入力など、ウィンドウに対して操作が行われることでプログラムが実行される仕組みをイベント駆動と呼びます。Windows API では、ウィンドウに対して発生したイベントがメッセージで届けられるのです。ウィンドウに送られてくるメッセージは数多くあるので、その中で処理したいものを振り分ける必要があります。

ゼロからはじめるWindows API - WinMain 関数 すべての始まり編
C言語の学習および環境の構築は下記を参考にしてください
1.ゼロからはじめるC言語 - 環境構築編
2.ゼロからはじめるC言語 - 関数編
3.ゼロからはじめるC言語 - 型・定数編
4.ゼロからはじめるC言語 - 変数編
5.ゼロからはじめるC言語 - 選択編
6.ゼロからはじめるC言語 - 繰り返し編
7.ゼロからはじめるC言語 - 配列編
8.ゼロからはじめるC言語 - 構造体編
9.ゼロからはじめるC言語 - 自作関数編
10.ゼロからはじめるC言語 - ポインタ編

Windows は、ハードウェアイベントが発生するとシステムが管理するメッセージキューにメッセージを追加し、アプリケーションの要求に従ってキューの先頭にあるメッセージを提供します。アプリケーションは、GetMessage() 関数でキューから次のメッセージを取得します。

GetMessage() 関数

BOOL GetMessage(
  LPMSG lpMsg,
  HWND hWnd,
  UINT wMsgFilterMin,
  UINT wMsgFilterMax
);

パラメータ

lpMsg メッセージを受け取る MSG 構造体のポインタ
hWnd メッセージの取得元となるウィンドウのハンドル。任意のウィンドウに送られたメッセージを受け取るには NULL
wMsgFilterMin 取得するメッセージの最小値
wMsgFilterMax 取得するメッセージの最大値

戻り値

WM_QUIT メッセージを取得すると 0、そうでなければ 1。エラーが発生した場合は -1

lpMsg パラメータに指定する値は、メッセージを表す MSG 構造体へのポインタです。MSG 構造体は、メッセージの ID や、メッセージの種類によって提供される追加情報などを格納します。

MSG 構造体

typedef struct {
    HWND hwnd;
    UINT message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD time;
    POINT pt;
} MSG, *PMSG;

メンバ

hwnd メッセージを受けるウィンドウのハンドル
message メッセージ ID
wParam メッセージの関連情報を提供するパラメータ
lParam メッセージの関連情報を提供するパラメータ
time メッセージが送られてからの経過時間
pt メッセージが送られた時のマウスカーソルの位置(スクリーン座標)

この構造体の message メンバに格納される値から、メッセージの種類を特定できます。メッセージ ID は WM_ から始まる定数が定義されているので、これと受信したメッセージの ID を比較して適切にイベントを処理します。wParam や lParam メンバに格納される値は、メッセージによって異なります。

GetMessage() 関数は、wMsgFilterMin パラメータと wMsgFilterMax パラメータに、受信するメッセージの最小値と最大値を指定して受け取るメッセージを限定するフィルタリングが可能です。すべてのメッセージを取得するには、両方のパラメータに 0 を渡してください。

通常、Windows アプリケーションは繰り返し文で GetMessage() 関数を呼び出し、メッセージの受信を監視します。これを、メッセージループと呼びます。受け取るべきメッセージがキューに存在しない場合、GetMessage() 関数は制御を返さずにメッセージの受信を待機します。よって、繰り返し処理が無意味に連続して CPU の負荷を高めることはありません。

GetMessage() 関数は、アプリケーションの終了を要求する WM_QUIT メッセージを取得すると 0 を返し、そうでなければ 1 を返します。取得したメッセージが WM_QUIT であるかどうかを比較するか、または GetMessage() 関数の結果が 0 であるかを調べ、WM_QUIT が発生したことを確認した場合はメッセージループを抜け出してください。関数が失敗した場合は 0 ではなく -1 が返されるので注意してください。

サンプル01

 #include <Windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    HWND hWnd;
    MSG msg;
    int messageResult;

    hWnd = CreateWindowEx(
        WS_EX_LEFT, TEXT("STATIC"), TEXT("なんくるないさー"),
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
        NULL, NULL, hInstance, NULL
    );
    if (hWnd == NULL) return 0;

    do {
        TCHAR text[1024];

        messageResult = GetMessage(&msg, NULL, 0, 0);

        wsprintf(text, TEXT("メッセージを受信しました\nMSG ID=%X\n\nプログラムを終了しますか?"), msg.message);
        if (MessageBox(hWnd, text, TEXT("メッセージ"), MB_YESNO) == IDYES)
            break;

    } while(messageResult == 1);

    return 0;
}

実行結果

このプログラムは、do 文を用いてメッセージループを作成しています。GetMessage() 関数でメッセージを取得し、その結果を r 変数に保存しています。r 変数の値が 1 であればメッセージループを続行し、そうでなければ抜け出します。

受信するメッセージは、単純な符号なし整数です。このプログラムでは、GetMessage() 関数で受け取ったメッセージの番号をメッセージボックスに表示しているだけです。アプリケーションに対して、定期的にメッセージが送られてくることを確認できます。

本来ならば受信したメッセージの ID に従って適切な処理を行わなければなりませんが、このプログラムはウィンドウで受け取ったメッセージを処理しないため、ウィンドウの移動やサイズ変更といった基本的な操作もできません。メッセージを受信するたび、繰り返しメッセージボックスを表示しているだけです。「はい」ボタンを押してプログラムを終了してください。

ウィンドウは、受信したメッセージの ID によってどのような操作が行われたのかを認識し、必要な処理を行います。幸い、ウィンドウに必要なすべての機能をアプリケーション開発者が記述する必要はありません。一般的なウィンドウの動作を行うための機能は用意されており、開発者はアプリケーションレベルで興味のあるメッセージを受信したときに目的のコードを実行するだけです。