メッセージクラッカによるコードの書き換え

前回までに書いてきた simple.cpp のウィンドウプロシージャは以下の通りです。

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

	int id;
	HWND hwndCtl;
	UINT codeNotify;
	PAINTSTRUCT ps;
	HDC hdc;
	RECT rect;

	switch( uMsg ) {
	case WM_CLOSE:
		if ( IDYES == MessageBox ( 
			hwnd,
			TEXT("Are you sure you want to quit this program?"),
			TEXT("Confirmation"),
			MB_YESNO ) ) {
			DestroyWindow(hwnd);
		}
		break;
		
	case WM_COMMAND:
		
		id         = (int) LOWORD(wParam);
		hwndCtl    = (HWND) lParam;
		codeNotify = (UINT) HIWORD(wParam);

		switch( id ) {
		case IDM_FILE_EXIT:
			
			SendMessage ( hwnd, WM_CLOSE, 0, 0 );
			break;
			
		case IDM_HELP_ABOUT:

			::MessageBox ( 
				NULL, 
				TEXT("Hello, About!"),
				TEXT("About This Program"), 
				MB_OK );
			
			break;		
		}
		break;
	case WM_PAINT:
		hdc = BeginPaint ( hwnd, &ps );
		GetClientRect ( hwnd, &rect );
		DrawText( 
			hdc, 
			TEXT("Hello, world!"), 
			-1, 
			&rect, 
			DT_CENTER | DT_VCENTER | DT_SINGLELINE
		);
		EndPaint( hwnd, &ps );
		break;
	case WM_SIZE:
		InvalidateRect( hwnd, NULL, TRUE );
		break;
	case WM_DESTROY:
		::PostQuitMessage( 0 );
		break;
	default:
		return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}

	return 0;
}

全ての処理がウィンドウプロシージャ内に記載されています。このままコードを書き足していくと、 異常に巨大な switch 文ができてしまい収集がつきません。

この問題に対処すべく、Windows SDK には メッセージクラッカ と呼ばれるマクロ群が用意されています。

メッセージクラッカは Windows SDK に付属する windowx.h に定義されています。

メッセージクラッカの仕組みについては、当サイト内の資料 メッセージクラッカ を参照してください。

メッセージクラッカを使ってコードを書き換えると、ウィンドウプロシージャは次のようになります。

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

	switch( uMsg ) {
	HANDLE_MSG( hwnd, WM_CLOSE, Main_OnClose );
	HANDLE_MSG( hwnd, WM_COMMAND, Main_OnCommand );
	HANDLE_MSG( hwnd, WM_DESTROY, Main_OnDestroy );
	HANDLE_MSG( hwnd, WM_PAINT, Main_OnPaint );
	HANDLE_MSG( hwnd, WM_SIZE, Main_OnSize );	
	default:
		return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}

	return 0;
}

だいぶすっきりしていませんか?

ここで登場した HANDL_MSG というのがマクロになってます。Main_OnClose, Main_OnCommand, ... というのは私が定義した関数です。それぞれ、以下の内容です。


void Main_OnClose( HWND hwnd ) {

	if ( IDYES == MessageBox ( 
		hwnd,
		TEXT("Are you sure you want to quit this program?"),
		TEXT("Confirmation"),
		MB_YESNO ) ) {

		DestroyWindow(hwnd);

	}
	
}


void Main_OnCommand( HWND hwnd, int id, HWND hwndCtl, UINT codeNotify ) {

	switch( id ) {
	case IDM_FILE_EXIT:
			
		SendMessage ( hwnd, WM_CLOSE, 0, 0 );
		break;
			
	case IDM_HELP_ABOUT:

		::MessageBox ( 
			NULL, 
			TEXT("Hello, About!"),
			TEXT("About This Program"), 
			MB_OK );
			
		break;		
	}
		
}


void Main_OnDestroy( HWND hwnd ) {

	::PostQuitMessage( 0 );

}


void Main_OnPaint( HWND hwnd ) {

	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;
	
	hdc = BeginPaint ( hwnd, &ps );
	GetClientRect ( hwnd, &rect );
	DrawText( 
		hdc, 
		TEXT("Hello, world!"), 
		-1, 
		&rect, 
		DT_CENTER | DT_VCENTER | DT_SINGLELINE
	);
	EndPaint( hwnd, &ps );

}


void Main_OnSize( HWND hwnd, UINT state, int cx, int cy ) {
	
	InvalidateRect( hwnd, NULL, TRUE );

}

それでは、どのようにしてこれらの関数のプロトタイプを決めれば良いか、説明します。

メッセージクラッカの使い方

一例として、もとの WM_COMMAND メッセージハンドラを、メッセージクラッカを用いて書き換える方法を説明します。

  1. windowsx.h をインクルードする
  2. windowsx.h を開く
  3. windowsx.h 内で WM_COMMAND を検索する
  4. Cls_OnCommand のプロトタイプをコメントからヘッダファイルにコピー&ペースト
    名前は変えても構いません。

  5. Cls_OnCommand を書く

    もとのウィンドウプロシージャの wParam や lParam から取得してきた値が、 関数への引数として渡されているので、それにあわせてコードも変更する。

  6. ウィンドウプロシージャの case WM_COMMAND: から break; を次の一文に置き換える。

    HANDLE_MSG( hwnd, WM_COMMAND, Cls_OnCommand );

上記の手順を WM_COMMAND 以外のメッセージにも適用します。すると上記のような関数とウィンドウプロシージャが出来上がります。

出来上がり

コードの断片ばかりでわかりにくかったかもしれませんので、それぞれのファイルを示します。

simple.h

#pragma once

#include <windows.h>

#define WND_CLASS_NAME TEXT("My_Window")

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

void Main_OnClose( HWND hwnd );
void Main_OnCommand( HWND hwnd, int id, HWND hwndCtl, UINT codeNotify );
void Main_OnDestroy( HWND hwnd );
void Main_OnPaint( HWND hwnd );
void Main_OnSize( HWND hwnd, UINT state, int cx, int cy );

simple.cpp


#include "simple.h"
#include <windowsx.h>
#include "resource.h"


int WINAPI WinMain(
	HINSTANCE hInstance, 
	HINSTANCE hPrevInstance, 
	LPSTR lpCmdLine, 
	int nCmdShow ) {

	HWND hWnd;
	WNDCLASSEX wcl;
	
	wcl.cbSize        = sizeof(WNDCLASSEX);
	wcl.hInstance     = hInstance;
	wcl.lpszClassName = WND_CLASS_NAME;
	wcl.lpfnWndProc   = WindowProc;
	wcl.style         = NULL;
	wcl.hIcon         = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APP));
	wcl.hIconSm       = NULL;
	wcl.hCursor       = LoadCursor(NULL, IDC_ARROW);
	wcl.lpszMenuName  = MAKEINTRESOURCE (IDR_MENU_MAIN);
	wcl.cbClsExtra    = 0;
	wcl.cbWndExtra    = 0;
	wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
	
	if(!RegisterClassEx(&wcl)) {
		return FALSE;
	}
	
	hWnd = CreateWindowEx(
		NULL, 
		WND_CLASS_NAME,
		TEXT("Windows Programming Primer - Simple"),
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		NULL,
		NULL,
		hInstance,
		NULL);
	
	if(!hWnd) {
		return FALSE;
	}
	
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	
	MSG msg;
	BOOL bRet;
	
	while( ( bRet = GetMessage( &msg, hWnd, 0, 0 ) ) != 0) { 

		if (bRet == -1) {
			break;
		}
		else {
			DispatchMessage( &msg ); 
		}
	}

	return msg.wParam;
	
}


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

	switch( uMsg ) {
	HANDLE_MSG( hwnd, WM_CLOSE, Main_OnClose );
	HANDLE_MSG( hwnd, WM_COMMAND, Main_OnCommand );
	HANDLE_MSG( hwnd, WM_DESTROY, Main_OnDestroy );
	HANDLE_MSG( hwnd, WM_PAINT, Main_OnPaint );
	HANDLE_MSG( hwnd, WM_SIZE, Main_OnSize );	
	default:
		return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}

	return 0;
}


void Main_OnClose( HWND hwnd ) {

	if ( IDYES == MessageBox ( 
		hwnd,
		TEXT("Are you sure you want to quit this program?"),
		TEXT("Confirmation"),
		MB_YESNO ) ) {

		DestroyWindow(hwnd);

	}
	
}


void Main_OnCommand( HWND hwnd, int id, HWND hwndCtl, UINT codeNotify ) {

	switch( id ) {
	case IDM_FILE_EXIT:
			
		SendMessage ( hwnd, WM_CLOSE, 0, 0 );
		break;
			
	case IDM_HELP_ABOUT:

		::MessageBox ( 
			NULL, 
			TEXT("Hello, About!"),
			TEXT("About This Program"), 
			MB_OK );
			
		break;		
	}
		
}


void Main_OnDestroy( HWND hwnd ) {

	::PostQuitMessage( 0 );

}


void Main_OnPaint( HWND hwnd ) {

	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;
	
	hdc = BeginPaint ( hwnd, &ps );
	GetClientRect ( hwnd, &rect );
	DrawText( 
		hdc, 
		TEXT("Hello, world!"), 
		-1, 
		&rect, 
		DT_CENTER | DT_VCENTER | DT_SINGLELINE
	);
	EndPaint( hwnd, &ps );

}


void Main_OnSize( HWND hwnd, UINT state, int cx, int cy ) {
	
	InvalidateRect( hwnd, NULL, TRUE );

}

makefile, resource.rc 及び resource.h は前回と全く同様です。

これでビルドができるはずです。動作は前回と全く同様になれば成功です。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説