Click here to Skip to main content
15,867,686 members
Articles / Desktop Programming / MFC
Article

Templates and MFC

Rate me:
Please Sign up or sign in to vote.
4.65/5 (12 votes)
27 Feb 2000CPOL 175.6K   2.4K   52   28
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)


Written By
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

 
QuestionHow to make it works in VS 2013? Pin
kadeokadeo20-Dec-15 11:57
kadeokadeo20-Dec-15 11:57 
GeneralNo good for template - MFC class Pin
Vitaly Tomilov17-Apr-08 0:13
Vitaly Tomilov17-Apr-08 0:13 
GeneralRe: No good for template - MFC class Pin
Len Holgate17-Apr-08 3:38
Len Holgate17-Apr-08 3:38 
Questionhow about typedef? Pin
code_discuss22-Apr-07 5:23
code_discuss22-Apr-07 5:23 
QuestionHow do you get BEGIN_TEMPLATE_MESSAGE_MAP to work with this? Pin
Tommy Keegan8-Aug-06 12:09
Tommy Keegan8-Aug-06 12:09 
AnswerRe: How do you get BEGIN_TEMPLATE_MESSAGE_MAP to work with this? Pin
Len Holgate8-Aug-06 12:34
Len Holgate8-Aug-06 12:34 
JokeI Finally Found a template that works with message maps Pin
Christopher Stratmann23-Mar-06 1:40
Christopher Stratmann23-Mar-06 1:40 
GeneralNew Version of this macro, compiles OK under VCpp.net Pin
ken_kovar29-Dec-04 6:49
ken_kovar29-Dec-04 6:49 
GeneralCompile error under VC7` Pin
Johnson Zhou28-May-03 5:08
Johnson Zhou28-May-03 5:08 
GeneralRe: Compile error under VC7` Pin
Len Holgate28-May-03 5:32
Len Holgate28-May-03 5:32 
GeneralRe: Compile error under VC7` Pin
Chen-Cha Hsu19-Nov-03 13:14
Chen-Cha Hsu19-Nov-03 13:14 
GeneralRe: Compile error under VC7` Pin
Anton Heidelwalder23-Dec-03 4:36
Anton Heidelwalder23-Dec-03 4:36 
Generalwaring C4211 on warning level 4 Pin
JimmyO13-Mar-03 0:42
JimmyO13-Mar-03 0:42 
GeneralRe: waring C4211 on warning level 4 Pin
Len Holgate15-Mar-03 6:09
Len Holgate15-Mar-03 6:09 
Generalunresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall Pin
DaviziNHr23-Dec-02 2:13
DaviziNHr23-Dec-02 2:13 
GeneralRe: unresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall Pin
DaviziNHr23-Dec-02 4:08
DaviziNHr23-Dec-02 4:08 
GeneralRe: unresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall Pin
Len Holgate23-Dec-02 20:48
Len Holgate23-Dec-02 20:48 
GeneralRe: unresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall Pin
Len Holgate23-Dec-02 9:48
Len Holgate23-Dec-02 9:48 
GeneralRe: unresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall Pin
DaviziNHr24-Dec-02 0:57
DaviziNHr24-Dec-02 0:57 
GeneralRe: unresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall Pin
DaviziNHr24-Dec-02 1:16
DaviziNHr24-Dec-02 1:16 
GeneralRe: unresolved external symbol "protected: virtual struct AFX_MSGMAP const * __thiscall Pin
Len Holgate24-Dec-02 4:46
Len Holgate24-Dec-02 4:46 
QuestionGreat code. How about templated RUNTIMECLASS? Pin
24-Apr-02 6:44
suss24-Apr-02 6:44 
GeneralVery useful! Pin
10-Sep-01 11:06
suss10-Sep-01 11:06 
GeneralAlternate for multiple params Pin
16-May-01 13:22
suss16-May-01 13:22 
GeneralGreat Article - Another Question Pin
John Gilbert13-Nov-00 12:11
John Gilbert13-Nov-00 12:11 

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

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