MFC - Templates

Page content

Templates are a great way of reusing code, unfortunately MFC makes it hard to write MFC friendly template classes…

The problem…

Templates are cool. Being able to write code that can work on objects of any type that satisfies the minimum functionality that you require make reusing code considerably easier. No more type-unsafe generic coding methods, like void *’s and macros. Templates solve all manner of problems.

Using templates with MFC classes, especially MFC classes with message maps isn’t easy. Why would you want to do so? Well, how about a class derived from a list box that can manage the lifetime of the objects displayed in it? You could put items into the list and store pointers to them in the item data and when the box is destroyed it can clean up and delete the objects for you. One problem with this is that at the point where the list box deletes your object it only has a void * pointer to it. Calling delete on that pointer wont call the object’s destructor.

Luckilly, although you have to jump through a few hoops, it’s not that difficult to make MFC classes work with templates. The main problem are the message map macros.

Damn MFC Macros!

If you look at the standard BEGIN_MESSAGE_MAP macro you can see the problem:

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

If the class you’re declaring a message map for is a template class then the macro expands all wrong… The template <class T> bits are missing. What you really need is something more like this….

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

The macro above recognises the fact that the class is a template and puts the template <class T>; bit in the right places.

You would use it like this…

BEGIN_TEMPLATE_MESSAGE_MAP(<class T>, TListBox<T>, CJBListBox)
    ON_WM_DESTROY()
END_MESSAGE_MAP()

Where TListBox<> is the derived class you’re setting up the mesage map for, CJBListBox is the base class and the <class T> bit is what ever is appropriate for your template definition.

When it comes to implementing the handler, you do it in the usual way…

template <class T>
afx_msg void TListBox<T>::OnDestroy()
{
   DeleteAll();

   CJBListBox::OnDestroy();
}

And because you KNOW the type of object that you’re storing in the list box’s item data ptr you can implement DeleteAll() like this:

template <class T>
void TListBox<T>::DeleteAll()
{
   int nMaxIndex = GetCount();

   for (int i = 0; i < nMaxIndex; i++)
   {
      T *pItem = (T*)GetItemDataPtr(i);

      if (-1 != (int)pItem)
      {
         delete pItem;
      }
   }

   ResetContent();
}

Of course, it’s not quite that easy. The class wizard often goes nuts when it sees these classes, so it’s best to fully implement the class for a particular data type before you turn it into a template. Alternatively, keep the original class wizard macro to hand, and just comment it out when you don’t need to do class wizard stuff.

BEGIN_TEMPLATE_MESSAGE_MAP(<class I>, TComGUIDListBox< I >, TListBox< CComGUID >)
//BEGIN_MESSAGE_MAP(TComGUIDListBox, TListBox<CComGUID>)
    //{{AFX_MSG_MAP(TComGUIDListBox)
    ON_CONTROL_REFLECT_EX(LBN_SELCHANGE, OnSelchange)
    ON_WM_DESTROY()
    ON_CONTROL_REFLECT_EX(LBN_SELCANCEL, OnSelcancel)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

If you comment the template version out and uncomment the standard version then you can add more message handlers using class wizard. Then you should switch the comments and adjust the generated code to suit.

The whole thing

Since there are different versions of the message map macros for when _AFXDLL is defined and when it’s not. The full code required is as follows:

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

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

#endif

That’s it.

The sample code shows this in action. First we define a list box that knows how to manage the lifetime of objects within it, then we derive another template class from it that knows how to populate itself from an STL style iterator that’s passed to its constructor.

Problems…

While I was tidying up the example code I realised that I needed a template class in the form:

template <class I, class T> class myclass;

Of course, as soon as I put this into the macro it broke… The problem is that the comma required between the <class I, class T> part of the template becomes two macro parameters… I quickly wrote a quick fix, a macro that explicitly took the extra template parameter. The problem was that this was ugly (I now had 2 template message map macros) and it wasn’t extensible (if I needed a template with 3 parameters, or if the base class needed multiple template parameteres, I had to change the complex macro to cope). At around this time I saw the message on the guest book from Dave Lorde mentioning a similar problem…

After some thought it became obvious that the problem could be solved by another level of indirection. I added the following simple macros to the top of the header file and removed the extra message map macro:

#define TEMPLATE_1(t1)                         t1
#define TEMPLATE_2(t1, t2)                     t1, t2
#define TEMPLATE_3(t1 ,t2 ,t3)                 t1, t2, t3

#define TCLASS_1(theClass, t1)                 theClass<t1>
#define TCLASS_2(theClass, t1, t2)             theClass<t1, t2>
#define TCLASS_3(theClass, t1, t2, t3)         theClass<t1, t2, t3>

The message map macro needed one final change (including the <>s for the initail template declaration)…

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

Now you can use the macro as follows:

BEGIN_TEMPLATE_MESSAGE_MAP(TEMPLATE_2(class I, class T), TCLASS_2(TComGUIDListBox, I, T), TListBox<T>)

wrapping multiple template parameters in the appropriate macros before use. The main message map macro never needs changing, if you need more parameters just add another simple macro pair to the top of the file. What’s more, these macros can be used when the base class has multiple parameters too!

Sample usage

When the dialog is initialised the list is created and passed the iterators it needs. There’s a horrible hack in here so that when it gets its first message (and we know the actual window exists!) we populate the list box from the iterators. When the box is destroyed we clean up. If anyone knows a better way to tell when a window has been created and actually exists so we can send messages to it, please tell me!

The nice thing about the code presented is that it can be used for any kind of object that can be iterated over by an iterator. The sample shows the same code being used for a list of GUIDs in the registry and a list of GUIDs obtained from the component category manager.

Download

The source for the template message map macros has been built under VC++ v5.0.

TemplateAFX.zip

See this code in action in a bigger application. This is the same example used by the Component Category Manager, JBListBox and IEnum articles.

Example1.zip

Revision history