EternalWindows
メッセージ管理 / ポストメッセージキュー

メッセージキュー内のメッセージは、ポストされたものから取得されるのが原則ですが、 厳密にはその限りではありません。 そもそも、これまで述べてきたメッセージキューという言葉は、 厳密に述べるとスレッドに存在する4つのキューの総称です。 GetMessageは優先して調べるキューを決めているため、 最初に調べられるキューにメッセージが存在した場合は、まずそのメッセージが取得されることになります。 次に、4つのキューを処理される順番が速いものから示します。

・送信メッセージキュー
・ポストメッセージキュー
・仮想入力キュー
・応答メッセージキュー(もっと優先順位が高いかもしれない)

今回を含め4回に渡って、個々のキューを検討していくことにします。 最初である本節では、ポストメッセージキューについて取り上げます。 このキューには以下のいずれかの関数が呼び出されることにより、 メッセージが格納されます。

・PostMessage
・PostThreadMessage

これらの関数はメッセージキューにメッセージをポストするだけですから、関数は直ちに制御を返します。 PostMessageは、引数のウインドウに関連付けられているスレッドにメッセージをポストし、 PostThreadMessageは特定のスレッドにメッセージをポストします。 スレッドにメッセージをポストするという言い方は聞き慣れないかもしれませんが、 メッセージキューはスレッド単位に存在しますから、間違った言い方ではありません。 PostThreadMessageは、次のように定義されています。

BOOL PostThreadMessage(
  DWORD idThread,
  UINT Msg,
  WPARAM wParam,
  LPARAM lParam
);

idThreadは、ポストの対象となるスレッドのIDを指定します。 Msgは、ポストしたいメッセージを指定します。 wParamは、WPARAMを指定します。 lParamは、LPARAMを指定します。

今回のプログラムは、PostThreadMessageを使用する例を示しています。

#include <windows.h>

DWORD WINAPI ThreadProc(LPVOID lpParameter);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static DWORD  dwThreadId = 0;
	static HANDLE hThread = NULL;

	switch (uMsg) {

	case WM_CREATE:
		hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, NULL, 0, &dwThreadId);
		return 0;

	case WM_LBUTTONDOWN:
		PostThreadMessage(dwThreadId, WM_NULL, 0, 0);
		return 0;

	case WM_DESTROY:
		if  (hThread != NULL)
			CloseHandle(hThread);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	WaitMessage();

	MessageBox(NULL, TEXT("スレッド"), TEXT("OK"), NULL);

	return 0;
}

PostThreadMessageでポストされたメッセージをGetMessageで取得した場合、 MSG構造体のhwndメンバはNULLが格納され、 DispatchMessageはメッセージの送信対象であるウインドウを特定することができません。 よって、この関数でカレントスレッドに対してメッセージをポストしても意味がありません。 この関数は主に、別スレッドに対して何らかの動作を促す場合に使用されるため、 今回のプログラムはWM_CREATEでスレッドを作成しています。 この作成されたスレッドは内部でWaitMessageを呼び出し、 メッセージキューにメッセージが格納されるまで処理を待機しています。 この待機を終了させるのが次の処理です。

case WM_LBUTTONDOWN:
	PostThreadMessage(dwThreadId, WM_NULL, 0, 0);
	return 0;

作成したスレッドのIDを基にPostThreadMessageを呼び出します。 これにより、作成したスレッドにWM_NULLがポストされることになりますから、 ThreadProcで呼ばれているWaitMessageは制御を返すことになります。 今回の場合、あくまでスレッドを動作させるためにメッセージをポストしているため、 ポストするメッセージの種類は問いません。 このため、WM_NULLという特に意味を持たないメッセージをポストしています。 スレッドではWaitMessageではなくGetMessageで待機することもできますが、 ポストされるメッセージが一種類だけである場合はメッセージを取得しても意味がないと思い、 WaitMessageを使用しています。

PostQuitMessageについて

WM_DESTROYで実行されるPostQuitMessageは、 スレッドがメッセージループから抜けるための大事な手段です。 具体的な仕組みとしては、PostQuitMessageによってWM_QUITメッセージがポストされ、 これを取得したGetMessageが0を返すことでメッセージループが終了することになるのですが、 この「WM_QUITメッセージをポスト」という言い方は、厳密に言うと正しくありません。 実は、スレッドは内部にキューステータスという一連のフラグを維持しており、 PostQuitMessageはその中のQS_QUITというフラグをONにするだけなのです。 これは、PostQuitMessageのプロトタイプを見ても頷けることです。

VOID PostQuitMessage(
  int nExitCode
);

戻り値がVOID型であるということは、この関数が決して失敗しないことを意味します。 理由は、この関数がメッセージのためのメモリを確保する設計になっておらず、 単純に内部フラグをONにするだけだからです。 ただし、メッセージループを抜けた際のMSG構造体のmessageはWM_QUITになっているため、 WM_QUITをポストしていると考えても特に問題はありません。



戻る