Click here to Skip to main content
Click here to Skip to main content

Templates and MFC

, 27 Feb 2000
Rate this:
Please Sign up or sign in to vote.
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.

Luckily, 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 recognizes 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 message 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 realized 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 parameters, 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 initial 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 initialized, 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.

See the article on Len's homepage for the latest updates.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Len Holgate
Software Developer (Senior) JetByte Limited
United Kingdom United Kingdom
Len has been programming for over 30 years, having first started with a Sinclair ZX-80. Now he runs his own consulting company, JetByte Limited and has a technical blog here.
 
JetByte provides contract programming and consultancy services. We can provide experience in COM, Corba, C++, Windows NT and UNIX. Our speciality is the design and implementation of systems but we are happy to work with you throughout the entire project life-cycle. We are happy to quote for fixed price work, or, if required, can work for an hourly rate.
 
We are based in London, England, but, thanks to the Internet, we can work 'virtually' anywhere...
 
Please note that many of the articles here may have updated code available on Len's blog and that the IOCP socket server framework is also available in a licensed, much improved and fully supported version, see here for details.

Comments and Discussions

 
GeneralNo good for template - MFC class PinmemberVitalyTomilov17-Apr-08 0:13 
GeneralRe: No good for template - MFC class PinmemberLen Holgate17-Apr-08 3:38 
Questionhow about typedef? Pinmembercode_discuss22-Apr-07 5:23 
QuestionHow do you get BEGIN_TEMPLATE_MESSAGE_MAP to work with this? PinmemberTommy Keegan8-Aug-06 12:09 
AnswerRe: How do you get BEGIN_TEMPLATE_MESSAGE_MAP to work with this? PinmemberLen Holgate8-Aug-06 12:34 
JokeI Finally Found a template that works with message maps Pinmemberchris17523-Mar-06 1:40 
GeneralNew Version of this macro, compiles OK under VCpp.net Pinmemberken_kovar29-Dec-04 6:49 
GeneralCompile error under VC7` PinmemberJohnson Zhou28-May-03 5:08 
GeneralRe: Compile error under VC7` PinmemberLen Holgate28-May-03 5:32 
GeneralRe: Compile error under VC7` PinmemberChen-Cha Hsu19-Nov-03 13:14 
GeneralRe: Compile error under VC7` PinmemberAnton Heidelwalder23-Dec-03 4:36 
Generalwaring C4211 on warning level 4 PinmemberJimmyO13-Mar-03 0:42 
GeneralRe: waring C4211 on warning level 4 PinmemberLen Holgate15-Mar-03 6:09 
Generalunresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall PinmemberDaviziNHr23-Dec-02 2:13 
GeneralRe: unresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall PinmemberDaviziNHr23-Dec-02 4:08 
GeneralRe: unresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall PinmemberLen Holgate23-Dec-02 20:48 
GeneralRe: unresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall PinmemberLen Holgate23-Dec-02 9:48 
GeneralRe: unresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall PinmemberDaviziNHr24-Dec-02 0:57 
GeneralRe: unresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall PinmemberDaviziNHr24-Dec-02 1:16 
GeneralRe: unresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall PinmemberLen Holgate24-Dec-02 4:46 
QuestionGreat code. How about templated RUNTIMECLASS? Pinmembersoichi24-Apr-02 6:44 
GeneralVery useful! PinmemberFrank Colbert10-Sep-01 11:06 
GeneralAlternate for multiple params PinmemberSteve Johnson16-May-01 13:22 
GeneralGreat Article - Another Question PinmemberJohn Gilbert13-Nov-00 12:11 
GeneralNot suitable for MFC PinsussJet29-Feb-00 21:23 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 28 Feb 2000
Article Copyright 2000 by Len Holgate
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid