メッセージループ

ShowWindow()関数を呼び出してウィンドウを表示した後で、アプリケーションの処理は次のループ処理に入る。

メッセージループ

    // メッセージループを実行
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

この部分は、とりあえずこう書く仕組みになっているものだと思っておいてよいだろう。GetMessage()でイベント待ちをし、イベントが発生したらTranslateMessage()とDispatchMessage()をコールする。

今回は、このメッセージループと、前回も取り上げたウィンドウプロシージャの関係を調べてみよう。どちらもイベントが発生すると動く仕組みになっており、どのような関係性にあるのかを把握しておきたいのだ。

メッセージループでは、MSG構造体の変数に発生したイベント情報が収められていることがわかる。MSG構造体は次のような構造体だ。

MSG構造体

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

MSG構造体の主なメンバは次のとおり。

MGS構造体のメンバ 内容
HWND hwnd メッセージを受信したウィンドウへのハンドル。メッセージがスレッドメッセージの場合はNULL。
UINT message メッセージ識別子。上位ワードはシステムに予約されており、アプリケーションは下位ワードのみを使うことができる。
WPARAM wParam メッセージに関する追加情報。
LPARAM lParam メッセージに関する追加情報。
DWORD time メッセージが投げられた時間。
POINT pt メッセージが投げられたときのスクリーン座標。

MSG構造体のmessageメンバに発生したイベント(メッセージ)の識別子(番号)が格納されている。このメッセージはウィンドウプロシージャでも使われているようなので、これをトレースすることでお互いどんなタイミングでイベントが発生しているのかを調べてみよう。

なお、MSG構造体、GetMessage()関数、TranslateMessage()関数、DispatchMessage()関数については次のページに説明がまとまっている。

メッセージをログファイルに記録する

そこで、どのようなメッセージが飛んできているのかについて、ファイルを記録して確認することにする。次の関数Log_uMsg()は指定したファイルに指定したメッセージ(UNIT)を記録するものだ。今回、この関数の説明は省略するが、ファイルに記録するものと考えていただければと思う。

Log_uMsg()関数

/*
 * MSG.messageを指定したファイルへ追記する関数
 */
void Log_uMsg(LPCWSTR fPath, UINT uMsg)
{
    HANDLE hFile;
    hFile = CreateFile(
        fPath, GENERIC_WRITE, 0, NULL,
    OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL
    );

    SetFilePointer(hFile, 0, NULL, FILE_END);

    TCHAR buf[1024];
    wsprintf(buf, TEXT("message: %d\n"), uMsg);

    DWORD dwNoW = 0;
    WriteFile(hFile, buf, lstrlen(buf)*sizeof(TCHAR), &dwNoW, NULL);

    CloseHandle(hFile);
}

この記録用の関数をメッセージループとウィンドウプロシージャの双方に仕込む。次のような感じでLog_uMsg()関数を挟んでおけばよい。

メッセージループにLog_uMsg()を仕込む

    // メッセージループを実行
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        Log_uMsg(TEXT("log1.txt"), msg.message);

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

ウィンドウプロシージャにLog_uMsg()を仕込む

/*
 * ウィンドウプロシージャ関数
 */
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    Log_uMsg(TEXT("log2.txt"), uMsg);

    switch (uMsg)
    {

これらコードをwinmain.cにマージして動作する状態にまとめると次のようになる。

winmain.c

/*
 * Reference:
 *    https://docs.microsoft.com/en-us/windows/win32/learnwin32/your-first-windows-program
 *    https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassa
 */

#ifndef UNICODE
#define UNICODE
#endif 

#include <windows.h>

#define BACKGROUND_COLOR (HBRUSH) (COLOR_WINDOW + 1)

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void Log_uMsg(LPCWSTR fPath, UINT uMsg);

/*
 * ウィンドウプログラムエントリポイント関数
 */
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
    // ウィンドウクラス名
    const wchar_t CLASS_NAME[]  = L"ウィンドウ作成の学習用プログラムクラス";

    // ウィンドウタイトル
    const wchar_t WINDOW_TITLE[]  = L"ウィンドウプログラミング学習";

    // ウィンドウクラス構造体を用意
    WNDCLASS wc;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WindowProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = CLASS_NAME;

    // style         ウィンドウクラススタイル
    // lpfnWndProc   ウィンドウプロシージャ
    // cbClsExtra    クラス構造体以降の追加確保分指定
    // cbWndExtra    ウィンドウインスタンス以降の追加確保分指定
    // hInstance     ウィンドウプロシージャインスタンス
    // hIcon         クラスアイコンハンドラ
    // hCursor       クラスカーソルハンドラ
    // hbrBackground ウィンドウ背景色
    // lpszMenuName  クラスメニューのリソース名
    // lpszClassName ウィンドウクラス名

    // ウィンドウクラス構造体を登録
    RegisterClass(&wc);

    // ウィンドウを作成
    HWND hwnd = CreateWindowEx(
        0,                   // 拡張ウィンドウスタイル
        CLASS_NAME,          // ウィンドウクラス名
        WINDOW_TITLE,        // ウィンドウタイトル
        WS_OVERLAPPEDWINDOW, // ウィンドウスタイル

        // サイズとポジション
        CW_USEDEFAULT,       // X座標
    CW_USEDEFAULT,       // Y座標
    1200,                // 幅
    800,                 // 高さ

        NULL,                // 親ウィンドウハンドラ
        NULL,                // メニューハンドラ
        hInstance,           // インスタンスハンドラ
        NULL                 // 追加のアプリケーションデータ
        );

    if (hwnd == NULL)
    {
        return 0;
    }

    // ウィンドウを表示
    ShowWindow(hwnd, nCmdShow);

    // メッセージループを実行
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        Log_uMsg(TEXT("log1.txt"), msg.message);

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

    return 0;
}

/*
 * ウィンドウプロシージャ関数
 */
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    Log_uMsg(TEXT("log2.txt"), uMsg);

    switch (uMsg)
    {
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);

            FillRect(hdc, &ps.rcPaint, BACKGROUND_COLOR);

            EndPaint(hwnd, &ps);
        }
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

/*
 * MSG.messageを指定したファイルへ追記する関数
 */
void Log_uMsg(LPCWSTR fPath, UINT uMsg)
{
    HANDLE hFile;
    hFile = CreateFile(
        fPath, GENERIC_WRITE, 0, NULL,
    OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL
    );

    SetFilePointer(hFile, 0, NULL, FILE_END);

    TCHAR buf[1024];
    wsprintf(buf, TEXT("message: %d\n"), uMsg);

    DWORD dwNoW = 0;
    WriteFile(hFile, buf, lstrlen(buf)*sizeof(TCHAR), &dwNoW, NULL);

    CloseHandle(hFile);
}

ではこのコードを実行してみよう。