WNDPROCをクラス化する。

 今更ながら、WNDPROCをクラス化する試みをしてみました。
 本当に今更ながらですが…

クラス化するにあたっての方針

 WindowsからユーザープログラムのWNDPROCへのメッセージは、グローバルな関数、もしくは、staticなメンバー関数にのみ可能。
 ということで、staticなメンバー関数から本命のメンバー関数にメッセージを送ってしまえば解決!
 ですが、staticなWNDPROCと、本命のWNDPROCが同じクラスにあると、ウィンドウのインスタンスとクラスのインスタンスの同期をとらなければならない都合上すべてのメッセージを処理することができなくなる…
 色々考えた末、ウインドウのインスタンスが生成されたあとに、クラスのインスタンスを生成するようにしてみました。
 この方法であれば、本命のWNDPROCで全てのメッセージを処理することも可能となります。

 以下、サンプルのソースファイルです。
 エラー処理はしていないので、そのあたりは各々で考えてください。

file: window.h

#pragma once

#include <tchar.h>
#include <windows.h>
#include <map>
#include <memory>

#ifndef BEGIN_MYLIB_NAMESPACE
#define BEGIN_MYLIB_NAMESPACE namespace mylib {
#define END_NAMESPACE }
#endif // !BEGIN_MYLIB_NAMESPACE

BEGIN_MYLIB_NAMESPACE

//*****************************************************************************
//HWNDとclassのインスタンスを関連付け、classのWndProcにメッセージを送信させる。
template<class TWindow>
class Window
{
private:
	//*** ウインドウメッセージ送信用プロシージャ
	static LRESULT CALLBACK refWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
		//*** HWNDとclassの関連付け用
		static std::map<HWND, std::shared_ptr<TWindow> > pWindows;

		if (!pWindows.count(hwnd)) {
			//インスタンスの生成と登録
			pWindows[hwnd] = std::make_shared<TWindow>(hwnd);
		}
		TWindow* pWnd = pWindows[hwnd].get();

		//WM_NCDESTROYに対する処理。
		if (msg == WM_NCDESTROY) {
			//最後のメッセージ処理。
			pWnd->WndProc(hwnd, msg, wParam, lParam);

			//必要無くなったclassのインスタンスを削除する。
			pWindows.erase(hwnd);
			return 0;
		}

		//メッセージ処理。
		return pWnd->WndProc(hwnd, msg, wParam, lParam);
	}

public:
	//RegisterClassExを利用してウインドクラスの登録。
	static ATOM Register(const LPWNDCLASSEX pWcex) {
		WNDCLASSEX wc = *pWcex;

		//変更されては困る値。
		wc.lpfnWndProc = refWndProc;
		wc.lpszClassName = TWindow::name;

		//ウインドウズに登録
		return ::RegisterClassEx(&wc);
	}

	//CreateWindowを利用してインスタンスを生成。
	static HWND Create(
		LPCTSTR lpWindowName,
		DWORD dwStyle,
		int x, int y, int nWidth, int nHeight,
		HWND hWndParent,
		HMENU hMenu,
		HINSTANCE hInstance,
		LPVOID lpParam
	) {
		return ::CreateWindow(
			TWindow::name, lpWindowName, dwStyle,
			x, y, nWidth, nHeight,
			hWndParent, hMenu, hInstance, lpParam
		);
	}

	//CreateWindowExを利用してインスタンスを生成。
	static HWND Create(
		DWORD dwExStyle,
		LPCTSTR lpWindowName,
		DWORD dwStyle,
		int x, int y, int nWidth, int nHeight, 
		HWND hWndParent,
		HMENU hMenu,
		HINSTANCE hInstance,
		LPVOID lpParam
	) {
		return ::CreateWindowEx(
			dwExStyle, TWindow::name, lpWindowName, dwStyle,
			x, y, nWidth, nHeight,
			hWndParent, hMenu, hInstance, lpParam
		);
	}
};

//*****************************************************************************

END_NAMESPACE

サンプルプログラム

 作製したライブラリを利用したサンプルプログラムがこちら。
 WNDPROC部分をクラス化する以外は、ほぼSDKを使ったプログラミングのままです。

file: test.cpp

#include "window.h"
#include <windowsx.h>

#define TOSTRING(x) _T(#x)

//*****************************************************************************
//*** カスタムウインドウの定義
class MainWindow
{
private:
	void Cls_OnDestroy(HWND hwnd);
	void Cls_OnPaint(HWND hwnd);

public:
	//必須:ウインドクラス名
	static constexpr TCHAR name[] = { TOSTRING(MainWindow) };

	//コンストラクターにはHWNDが送られてくる。
	MainWindow(HWND hwnd) {}

	//必須:メンバー名は変えられない。
	LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
};

//-----------------------------------------------------------------------------
LRESULT MainWindow::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	switch (msg) {
		HANDLE_MSG(hwnd, WM_DESTROY, Cls_OnDestroy);
		HANDLE_MSG(hwnd, WM_PAINT, Cls_OnPaint);
	}
	return ::DefWindowProc(hwnd, msg, wParam, lParam);
}

//-----------------------------------------------------------------------------
void MainWindow::Cls_OnDestroy(HWND)
{
	::PostQuitMessage(0);
}

//-----------------------------------------------------------------------------
void MainWindow::Cls_OnPaint(HWND hwnd)
{
	PAINTSTRUCT	ps;
	HDC	hdc = ::BeginPaint(hwnd, &ps);

	RECT rc{ 0 };
	::GetClientRect(hwnd, &rc);
	::DrawText(
		hdc,
		_T("Hello,Windows!"),
		-1,
		&rc,
		DT_SINGLELINE | DT_CENTER | DT_VCENTER
	);

	::EndPaint(hwnd, &ps);
}

//*****************************************************************************
//*** メインルーチン
int WINAPI _tWinMain(
	_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE,
	_In_ LPTSTR,
	_In_ int nCmdShow)
{
	//カスタムウインドウの登録
	WNDCLASSEX wc{ 0 };
	wc.cbSize = sizeof(WNDCLASSEX);
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
	wc.hIcon = ::LoadIcon(NULL, IDI_APPLICATION);
	wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
	wc.hIconSm = ::LoadIcon(NULL, IDI_APPLICATION);
	wc.hInstance = hInstance;

	if (!mylib::Window<MainWindow>::Register(&wc)) { return 1; }

	//ウィンドウを生成する。
	HWND hwnd = mylib::Window<MainWindow>::Create(
		_T("Hello,Windows!"),
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
		nullptr, nullptr, hInstance, nullptr
	);
	if (!hwnd) { return 2; }

	::ShowWindow(hwnd, nCmdShow);
	::UpdateWindow(hwnd);

	MSG msg;

	while (::GetMessage(&msg, NULL, 0, 0) > 0)
	{
		::TranslateMessage(&msg);
		::DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

//*****************************************************************************

 クラス側のWndProcでデフォルト処理をするので、SDIでもMDIでもプログラミング可能です。
 また、ウインドウのインスタンスからクラスのインスタンスを生成ので、リソーススクリプトでのカスタムコントロールなども対応可能となっています。

(2025/08/24:改定)
(2024/05/06:作成)