Templatizing Your MFC Message Maps

On reading about the
Template
Metaprograms
, I wondered if they may help building message maps without MFC,
something like C message crackers, but simpler to use. I wanted to make it possible
to write something like this statement once in the class header:


class CClass : public CBase
{
..
START_DESCRIPTING
DECL_MSG_HANDLER1(WM_SIZE, void, Size)(UINT, int cx, int cy)
{ _cx = cx; _cy = cy; }
DECL_MSG_HANDLER1(WM_PAINT, void, Paint)();
DECL_MSG_HANDLER(WM_CAPTURECHANGED, CaptureChanged);
DECL_MSG_HANDLER1(WM_DESTROY, void, Destroy)();
END_DESCRIPTING
..
};

I found a solution in the result. Some ideas put in this solution seem to be quite interesting and worthy sharing.

All objects of the given class share single object containing array of HandlerInfo structs. Each class has a template by message CDescriptor class, which iteratively initializes this array:


struct HandlerInfo
{
unsigned int iMsg;
LRESULT (*pHandler)(CBase*, WPARAM, LPARAM);
private:
//No implementation
HandlerInfo& operator = (HandlerInfo&);
};

inline bool operator < (const HandlerInfo& info, unsigned int iMsg) { return info.iMsg < iMsg; } inline bool operator < (unsigned int iMsg, const HandlerInfo& info) { return iMsg < info.iMsg; }

Base class CBase uses these arrays to invoke proper message handlers.

From the header file:


class CBase
{
private:
virtual int GetHandlerInfo(const HandlerInfo*&)
{
return 0;
}

virtual LRESULT DefProc(UINT iMessage,
WPARAM wParam,
LPARAM lParam) = 0;
public:
LRESULT WndProc( UINT iMessage,
WPARAM wParam,
LPARAM lParam );
};

From the source file:


LRESULT CBase::WndProc( UINT iMessage,
WPARAM wParam,
LPARAM lParam )
{
const HandlerInfo* pInfo = NULL;
int nCount = GetHandlerInfo(pInfo);
if(pInfo)
{
const HandlerInfo* pLast = pInfo + nCount;
pInfo = std::lower_bound(pInfo, pLast, iMessage);
if(pInfo != pLast && pInfo->iMsg == iMessage)
return pInfo->pHandler(this, wParam, lParam);
}
return DefProc(iMessage, wParam, lParam );
}

The purpose of the template class CInvoker is to match class functions to HANDLE_ macros from the <windowsx.h> header. It allows transformation of member function call syntax to one of pseudo standalone function call. There is a problem: though the latest C++ standard allows writing return void, VC compiler does not allow it. So here goes a hack: we comment out return statements and disable corresponding compiler error. Since functions return integer and pointer types in the EAX register at x86 processors, they are safely passed out as outer function results. There is also no problem in the void case:


template<typename R, typename T>
class CInvoker
{
public:
CInvoker(T const& r) : m_r(r) {}

LRESULT operator () (CBase* pC)
{
return
(pC->*reinterpret_cast<LRESULT (CBase::*)()>
(static_cast<R (CBase::*)()>(m_r)))();
}

#pragma warning(disable:4716)

template<typename P>
R operator () (CBase* pC, P p)
{
// return
(pC->*static_cast<R (CBase::*)(P)>(m_r))(p);
}

template<typename P1, typename P2>
R operator () (CBase* pC, P1 p1, P2 p2)
{
// return
(pC->*static_cast<R (CBase::*)
(P1, P2)>(m_r))(p1, p2);
}

template<typename P1, typename P2, typename P3>
R operator () (CBase* pC, P1 p1, P2 p2, P3 p3)
{
// return
(pC->*static_cast<R (CBase::*)
(P1, P2, P3)>(m_r))(p1, p2, p3);
}

template<typename P1,
typename P2,
typename P3,
typename P4>

R operator () (CBase* pC, P1 p1, P2 p2, P3 p3, P4 p4)
{
// return
(pC->*static_cast<R (CBase::*)(P1, P2, P3, P4)>
(m_r))(p1, p2, p3, p4);
}

#pragma warning(default:4716)

private:
T const& m_r;

//No implementation
CInvoker& operator = (CInvoker&);
};

Template GetInvoker() function is here for converting implicit template parameter to explicit one.


template<typename R, typename T>
inline CInvoker<R, T> GetInvoker(T const& r)
{
return CInvoker<R, T>(r);
}

DECL_MSG_HANDLER1 and DECL_MSG_HANDLER2 macros are used for standard Windows messages handling. The former one invokes one function for a message, the latter one one function for two messages. They do the next things:
1. Describe static functions with uniform return types and parameters lists: static LRESULT Invoke##_msg(CBase* pC, WPARAM wParam, LPARAM lParam). These functions make calls to real methods of classes pointed by pC. These functions also use macros from the <windowsx.h> header to transform wParam and lParam to proper methods arguments.
2. Template CDescriptor specialization for the actual message value.
3. Heading part of the handler method of the class declaration.
Both these function include DECL_MSG_HANDLER_ macro to describe the repeating part of declaration.


#define DECL_MSG_HANDLER_(msg, _msg, rettype, handler) /
static LRESULT Invoke##_msg(CBase* pC, /
WPARAM wParam, /
LPARAM lParam) /
{ /
wParam; lParam; /
return /
HANDLE##_msg(pC, wParam, lParam, /
(GetInvoker<rettype>(handler))); /
} /
/
template<> class CDescriptor<msg> /
{ /
public: /
enum { previous = msg }; /
static int GetOffset() /
{ /
return CDescriptor<CDescriptor<msg-1>::previous> /
::GetOffset()+1; /
} /
static void Setup(HandlerInfo* pInfo) /
{ /
CDescriptor<CDescriptor<msg-1>::previous> /
::Setup(pInfo); /
pInfo[GetOffset()].iMsg = msg; /
pInfo[GetOffset()].pHandler = Invoke##_msg; /
} /
};

#define DECL_MSG_HANDLER1(msg, rettype, handler)
DECL_MSG_HANDLER_(msg, _##msg, rettype, handler)
rettype handler

#define DECL_MSG_HANDLER2(msg1, msg2, rettype, handler)
DECL_MSG_HANDLER_(msg1, _##msg1, rettype, handler)
DECL_MSG_HANDLER_(msg2, _##msg2, rettype, handler)
rettype handler

DECL_MSG_HANDLER macros do the same thing for the non-standard massages. They dont use macros from the <windowsx.h> header.


#define DECL_MSG_HANDLER(msg, handler)
static LRESULT Invoke_##msg(CBase* pC,
WPARAM wParam,
LPARAM lParam)
{
return
(pC->*static_cast<LRESULT (CBase::*)(WPARAM,
LPARAM)> (handler))(wParam, lParam);
}

template<> class CDescriptor<msg>
{
public:
enum { previous = msg };
static int GetOffset()
{
return CDescriptor<CDescriptor<msg-1>::previous>
::GetOffset()+1;
}
static void Setup(HandlerInfo* pInfo)
{
CDescriptor<CDescriptor<msg-1>::previous>
::Setup(pInfo);
pInfo[GetOffset()].iMsg = msg;
pInfo[GetOffset()].pHandler = Invoke_##msg;
}
};
LRESULT handler(WPARAM wParam, LPARAM lParam)

START_DESCRIPTING macro declares template class CDescriptor and specializes it for the boundary values.


#define START_DESCRIPTING

public:
template<int msg> class CDescriptor
{
public:
enum
{
previous = CDescriptor<msg-1>::previous,
};
static int GetOffset()
{
return CDescriptor<previous>::GetOffset();
}
static void Setup(HandlerInfo* pInfo)
{
CDescriptor<previous>::Setup(pInfo);
}
};

template<>
class CDescriptor<0>
{
public:
enum { previous = 0 };
static int GetOffset()
{
return -1;
}
static void Setup(HandlerInfo* ) {}
};

template<>
class CDescriptor<MSG_USR_FIRST-1>
{
public:
enum { previous = WM_USER };
};

END_DESCRIPTING macro declares single object containing array of HandlerInfo structs and returning it method.


#define END_DESCRIPTING
private:
class CHandlerInfo
{
public:
const int m_numMsgEntries;
HandlerInfo* const m_arrHandlers;
CHandlerInfo()
: m_numMsgEntries(CDescriptor<MSG_USR_LAST>
::GetOffset() + 1),
m_arrHandlers (new HandlerInfo[m_numMsgEntries])
{
CDescriptor<MSG_USR_LAST>::Setup(m_arrHandlers);
}
~CHandlerInfo() { delete[] m_arrHandlers; }

private:
CHandlerInfo& operator = (CHandlerInfo&);
};
int GetHandlerInfo(const HandlerInfo*& rpHandlerInfo)
{
static CHandlerInfo info;
rpHandlerInfo = info.m_arrHandlers;
return info.m_numMsgEntries;
}

MSG_USR_FIRST and MSG_USR_LAST constants define limits of the user messages area. For example:


const UINT MSG_USR_FIRST = WM_USER + 0x4000;
const UINT MSG_USR_LAST = MSG_USR_FIRST + 0x0100;

I used the Splitter
Demo
of Reliable
Software
to test this approach. Two files (MessageMapping.h and MessageMapping.cpp)
were added; changes made by me are marked with comments.

Downloads

Download source – 17 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read