MFCのCWndから派生したクラスをテンプレートにする

普通にクラスをクラステンプレートにする方法については、説明しない。
MFCのCWndの派生ツリーにあるクラスから派生した、メッセージマップを持つクラスを、クラステンプレートにするにあたって必要になる小細工。以前、「そのうち書こう」とした話。


CWndから派生したクラスは、メッセージマップを持つ。こんなの。


DerivedWnd.h

class CDerivedWnd : public CWnd
{
	...
	DECLARE_MESSAGE_MAP()
	...
};


DerivedWnd.cpp

BEGIN_MESSAGE_MAP(CDerivedWnd, CWnd)
	//{{AFX_MSG_MAP(CDerivedWnd)
	...
	ON_WM_CREATE()
	ON_WM_DESTROY()
	ON_WM_TIMER()
	...
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()


この、メッセージマップを構成するマクロ、"DECLARE_MESSAGE_MAP"、"BEGIN_MESSAGE_MAP"、"END_MESSAGE_MAP" は、afxwin.hに定義がある。


afxwin.h

struct AFX_MSGMAP_ENTRY;       // declared below after CWnd

struct AFX_MSGMAP
{
#ifdef _AFXDLL
	const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
#else
	const AFX_MSGMAP* pBaseMap;
#endif
	const AFX_MSGMAP_ENTRY* lpEntries;
};

#ifdef _AFXDLL
#define DECLARE_MESSAGE_MAP() \
private: \
	static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
	static AFX_DATA const AFX_MSGMAP messageMap; \
	static const AFX_MSGMAP* PASCAL _GetBaseMessageMap(); \
	virtual const AFX_MSGMAP* GetMessageMap() const; \

#else
#define DECLARE_MESSAGE_MAP() \
private: \
	static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
	static AFX_DATA const AFX_MSGMAP messageMap; \
	virtual const AFX_MSGMAP* GetMessageMap() const; \

#endif

#ifdef _AFXDLL
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
	const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \
		{ return &baseClass::messageMap; } \
	const AFX_MSGMAP* theClass::GetMessageMap() const \
		{ return &theClass::messageMap; } \
	AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
	{ &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; \
	AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
	{ \

#else
#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
	const AFX_MSGMAP* theClass::GetMessageMap() const \
		{ return &theClass::messageMap; } \
	AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
	{ &baseClass::messageMap, &theClass::_messageEntries[0] }; \
	AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
	{ \

#endif

#define END_MESSAGE_MAP() \
		{0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
	}; \


この内、"BEGIN_MESSAGE_MAP" と "END_MASSAGE_MAP" のおかげで、クラスをただテンプレートにしてやっても、おとなしくコンパイルされてくれない。テンプレート引数が、考慮されていないからだ。
だから、以下のような、テンプレート引数を渡せるようなマクロを、新規ヘッダ MessageMapTemplate.h に定義することにする。


DerivedWnd.cpp

#include "MessageMapTemplate.h" // 追加
  ...
BEGIN_MESSAGE_MAP_TEMPLATE(typename ParamType, CDerivedWnd< ParamType >, CWnd)  // 変更
	//{{AFX_MSG_MAP(CDerivedWnd)
	...
	ON_WM_CREATE()
	ON_WM_DESTROY()
	ON_WM_TIMER()
	...
	//}}AFX_MSG_MAP
END_MESSAGE_MAP_TEMPLATE()  // 変更


上記の仕様に合うように、マクロを定義する。


MessageMapTemplate.h

/*!
* @file MessageMapTemplate.h
* @brief MFC のメッセージマップを template に対応させるマクロ
*/
#if ! defined( MESSAGE_MAP_TEMPLATE_H__ )
#define MESSAGE_MAP_TEMPLATE_H__

#ifdef _AFXDLL
#define BEGIN_MESSAGE_MAP_TEMPLATE(param, theClass, baseClass) \
	template< param > \
	const AFX_MSGMAP* PASCAL theClass::_GetBaseMessageMap() \
		{ return &baseClass::messageMap; } \
	template< param > \
	const AFX_MSGMAP* theClass::GetMessageMap() const \
		{ return &theClass::messageMap; } \
	template< param > \
	AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
	{ &theClass::_GetBaseMessageMap, &theClass::_messageEntries[0] }; \
	template< param > \
	AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
	{ \

#else
#define BEGIN_MESSAGE_MAP_TEMPLATE(param, theClass, baseClass) \
	template< param > \
	const AFX_MSGMAP* theClass::GetMessageMap() const \
		{ return &theClass::messageMap; } \
	template< param > \
	AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
	{ &baseClass::messageMap, &theClass::_messageEntries[0] }; \
	template< param > \
	AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
	{ \

#endif

#endif  // ! defined( MESSAGE_MAP_TEMPLATE_H__ )


そして、DerivedWnd.h の末尾、クラス宣言の外に、以下の include を追加する。


DerivedWnd.h

	...
#include "DerivedWnd.cpp"


最後に、プロジェクトの設定を変更して、DerivedWnd.cpp を、コンパイルしないようにする。つまり、ヘッダに定義をすべて含めていることになる。これは、Visual C++が、クラステンプレートの定義をヘッダの外に置くことに対応していないため。


Visual C++ 6.0であれば、"//{AFX_MSG_MAP(CDerivedWnd)" 等の目印コメントで騙して、クラスウィザードを使うことができる。
Visual C++ 7.0以降は、これらの目印コメントを使わなくなってしまったので、メッセージマップはすべて手書きすることになる。