EternalWindows
基礎 / ウインドウプロシージャ

前節では、ウインドウにイベントが発生すると ウインドウプロシージャという特別な関数が呼ばれると説明しました。 今回は、この関数の内部構造について見ていきたいと思います。

LRESULT CALLBACK WindowProc(
   HWND hwnd,
   UINT uMsg,
   WPARAM wParam,
   LPARAM lParam
);

まず、WindwowProcという関数名ですがこれは自由に命名して構いません。 重要なのは、ウインドウプロシージャ(上記の例ではWindowProc)のアドレスを システムに対して登録することなので、関数名事体は重要な意味を持たないのです。 ウインドウプロシージャの設定は、WinMainの次のコードで行われています。

wc.lpfnWndProc = WindowProc;

WinMainのコードは基本的に変更することはありませんが、 もし、ウインドウプロシージャの名前を変更したいような場合は、 上記コードのWindwProcという名前も変更するようにしてください。

続いて引数の説明に入ります。 hwndは、メッセージを受け取るウインドウのハンドルが格納されています。 メッセージとは、いわば発生したイベントを表す定数のことで、 それがuMsgに格納されることになります。 wParamとlParamは、メッセージの追加情報です。 これについては、後の節で詳しく取り上げます。 戻り値のLRESULTは、メッセージの処理結果を表すため使う型です。 これについては、次節で取り上げます。 CALLBACKという修飾子は、WinMainに付いているWINAPIと同じ意味を持ちますが、 なるべくWINAPIではなくCALLBACKと付けるようにしてくだい。 コールバックというのは、何らかのデータの発生や取得を知らせる意味を持つため、 ウインドウプロシージャの場合はCALLBACKとするほうが意味合い的に正しいからです。 ちなみに、CALLBACKを付けた関数のことをコールバック関数と呼びますが、 WindowProcの場合はウインドウプロシージャと呼ぶことが多いようです。

一辺に色々なことを述べましたが、最低限理解しておくべきことはuMsgにメッセージが格納されているという点のみです。 この値を確認すればどのようなイベントが発生したかを特定できるため、 自分の目的にあったメッセージを処理することが可能となります。 たとえば、前節で示したマウスの左ボタンが押されたときに メッセージボックスを表示するというコードは、次のように書くことができます。

if (uMsg == WM_LBUTTONDOWN) {
	MessageBox(NULL, TEXT("左ボタンが押されました。"), TEXT("OK"), MB_OK);
}

このWM_XXXとされている定数が、正にメッセージを表しています。 XXXの部分には、そのメッセージの意味を推測できる文字が入ることになっており、 たとえば、LBUTTONDOWNなら「左ボタンが押された」ということだと推測できます。 このような定数は、windows.hが内部でインクルードしているwinuser.hに定義されているため、 一度目を通しておくとよいでしょう。

#define WM_LBUTTONDOWN                  0x0201
#define WM_LBUTTONUP                    0x0202
#define WM_LBUTTONDBLCLK                0x0203
#define WM_RBUTTONDOWN                  0x0204
#define WM_RBUTTONUP                    0x0205
#define WM_RBUTTONDBLCLK                0x0206

※winuser.hから抜粋

ここからも分かるように、実はメッセージというのは単なる数値に過ぎません。 ただ単純に0x201という数値ではそれが何を意味するのかを推測できないため、 WM_LBUTTONDOWNというような形で人間に分かりやすく定義しているのです。 恐らく、WMとはWindowMessageの略ではないかと思われます。

では、これまでのことを基にWindowProcの内部を確認していきたいと思います。

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

このWindowProcには一見、先に示したif(uMsg==WM_XXX)というコードが見当たりませんが、 よく見てみるとswitch,case文が存在することに気付くはずです。 実はWindowProcでのメッセージの処理判定には、 if文を使わずswitch,case文を使うという標準があるため、 できるだけそれに倣うべきとされています。

case WM_DESTROY:
	PostQuitMessage(0);
	return 0;

このWM_DESTROYは、今回処理している唯一のメッセージです。 WM_DESTROYはウインドウが破棄されようとしているとき送られ、 このメッセージから制御を返すとウインドウは完全に破棄されます。 つまり、ウインドウはもう見ることはできなくなっており、 アプリケーションは終了すべき時点まで来ていることになります。 PostQuitMessageという関数を呼び出すとアプリケーションの終了を促すことができるため、 WM_DESTROYでは必ずPostQuitMessageを呼ぶようにしてください。

随分と長い話になりましたが、以上がWindowsプログラミングの基礎となります。 今回のプログラムは、マウスの左ボタンが押されるとメッセージボックスを 表示するというものですが、これまでの話を理解していれば非常に簡単であるはずです。

#include <windows.h>

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)
{
	switch (uMsg) {

	case WM_LBUTTONDOWN:
		MessageBox(hwnd, TEXT("左ボタンが押されました。"), TEXT("OK"), MB_OK);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

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

まず、関数の前方宣言について確認しておきましょう。 冒頭のwindows.hのインクルードの後に、次のコードがあります。

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

このように前方宣言しているのは、ウインドウプロシージャを設定するために、WinMain内部でWindowProcを参照しているからです。 勿論、WindowProcの実装をWinMainより前に書いたなのならこの宣言は不要です。

さて、今回すべきことはマウスの左ボタンを押したときの処理ですから、 まずはその瞬間を捕らえなければなりません。 既に述べてきたように、ウインドウ上でマウスの左ボタンを押したときにはWM_LBUTTONDOWNが送られるため(uMsgにWM_LBUTTONDOWNが格納される)、 switch文にWM_LBUTTONDOWNのcase文を追加します。

case WM_LBUTTONDOWN:
	MessageBox(hwnd, TEXT("左ボタンが押されました。"), TEXT("OK"), MB_OK);
	return 0;

これで、マウスの左ボタンが押されたときにメッセージボックスが表示されるようになります。 今回のMessageBoxは第1引数にウインドウハンドルを指定していますが、 このようにした場合メッセージボックスに応答するまで ウインドウを操作できないという効果をもたせることがでます。

これまでの話から分かるように、ウインドウプロシージャはあらゆるメッセージを処理する関数として成り立ち、 他の関数でメッセージを考慮しなくてもよいという効果をもたらしていますが、 もう1つ素晴らしい特徴を持ち合わしています。 それは、あらゆるメッセージをこの関数に集約したおかげで、 あらゆるメッセージのデフォルト処理も行うことができるという点です。

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

このDefWindowProcという関数を呼び出した場合、 uMsgで表されるメッセージのデフォルト処理が内部で行われることになります。 これはつまり、自分が処理したくないようなメッセージは、 DefWindowProcに任せることができることを意味しています。 たとえば、ウインドウの「閉じる」ボタンが押されたらウインドウを閉じなければなりませんが、 そのような方法は現段階では全く分かりませんし、 何より「ウインドウを閉じる」といったどのプログラムにでも必要な処理を わざわざコードとして書くのは大変煩わしいものです。 しかし、DefWindowProcは全てのメッセージのデフォルト処理を把握していますから、 この関数を呼び出すということは、送られてきたメッセージを間接的に処理しているのと同じ意味を持つのです。

default:
	break;

switch文のcase文で処理しなかったメッセージがあった場合、 このdefaultという構文が実行されることになります。 今回の場合、uMsgがWM_LBUTTONDOWNとWM_DESTROY以外の場合はこの構文が実行されますから、 ここでbreakをするということはDefWindowProcが呼び出される、 つまり、上記2つのメッセージ以外はデフォルトの処理を行うということを意味しています。 したがって、このコードはあらゆるメッセージを捕まえて、 そこでDefWindowProcを呼ぶようなことはしなくてもよいという 大きな間接化をもたらしていると考えることができます。


戻る