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


Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Ever-increasing workloads and the challenge of containing costs leave companies conflicted by the need for increased processing capacity while limiting physical expansion. Migration to HP's new generation of increased-density rack-and-blade servers can address growing demands for compute capacity while reducing costly sprawl. Sponsored by: HP and Intel® Xeon® processors Intel, the Intel logo, and Xeon Inside are trademarks of Intel Corporation in the U.S. and/or other countries. HP is the sponsor …

  • When it comes to desktops – physical or virtual – it's all about the applications. Cloud-hosted virtual desktops are growing fast because you get local data center-class security and 24x7 access with the complete personalization and flexibility of your own desktop. Organizations make five common mistakes when it comes to planning and implementing their application management strategy. This eBook tells you what they are and how to avoid them, and offers real-life case studies on customers who didn't …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds