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以降は、これらの目印コメントを使わなくなってしまったので、メッセージマップはすべて手書きすることになる。