Creating a Practical C++ Template

Environment: VC6, VC.Net, Win32

Introduction

Most C++ books and articles love to use containers or mathematical functions (like compile time recursion) to represent the power of the template. Yeah, it is cool, but it seems only a scientific programmer can benefit from meta programming. In this article, I'll show how an application developer can enjoy the fun of programming templates, too.

  1. Remove code duplication.

    Imagine I build a MFC dialog box that has two different kinds of button, a CBitmapButton and normal CButton; I use two CArray instances, respectively, to hold them. Therefore, in the .h file, I'll have the following declaration:

    #include<afxtempl.h>
    class MyDialog
    {
    //....
    private:
      CArray<CButton,CButton&> m_buttonArray;
      CArray<CBitmapButton,CBitmapButton&> m_bmpButtonArray;
    };
    
    Now, I need a function to hide all the buttons on the dialog box. The first iteration needed to write the function is:
    void MyDialog::ShowAllButtons(BOOL bShow)
    {
      int nIndex=0;
      for (nIndex=0;nIndex < m_buttonArray.GetSize();++nIndex)
    
    {
        m_buttonArray[nIndex].ShowWindow(bShow);
    
    }
        for (nIndex=0;nIndex<m_bmpButtonArray.GetSize();++nIndex)
    
    {
        m_bmpButtonArray[nIndex].ShowWindow(bShow);
    
    }
    }
    
    It seems what I need is to copy and paste and change the variable names, but it smells bad for a real programmer who thinks programming is more than a job but also is an art. You can imagine that, if there are 10 different types of button arrays, I have to use Ctrl+C and Ctrl+V 10 more times; it sucks. There must be something that can be done, and now the template kicks in.

    Let's add one more template function to the MyDialog; let's call it ShowButtons. Now, the .h file becomes:
    class MyDialog
    {
    //....
    private:
      void ShowAllButtons(BOOL bShow);
      //new template
    function
      template <typename ButtonType>
    
    void ShowButtons(CArray<ButtonType,ButtonType&>
                     &rButtonArray,BOOL bShow);
    private:
    CArray<CButton,CButton&> m_buttonArray;
    CArray<CBitmapButton,CBitmapButton&> m_bmpButtonArray;
    };
    
    We'll define it as follows (there is still no export keyboard supported in VC++ yet, so I defined the function in the same .h file).
    template <typename ButtonType>
    void
    CMyDialog::ShowButtons(CArray<ButtonType,ButtonType&>
                           &rButtonArray,BOOL bShow)
    {
      for (int nIndex=0;nIndex<rButtonArray.GetSize();++nIndex)
    
    {
    
    
    rButtonArray[nIndex].ShowWindow(bShow);
    
    }
    }
    
    and rewrite the ShowAllButtons as follows:
    void
    CMyDialog::ShowAllButtons(BOOL bShow)
    {
      ShowButtons(m_buttonArray,bShow);
      ShowButtons(m_bmpButtonArray,bShow);
    }
    
    the compiler will deduce the type automatically. You are also welcome to call the ShowButtons with an explicit type specified, as in:
    ShowButtons<CButton>(m_buttonArray,bShow);
    ShowButtons<CBitmapButton>(m_bmpButtonArray,bShow);
    
    These both work, and now, there is no need to copy and paste anymore because the code is generated by the compiler through the template. This is the power of C++ versus VB or any other language.


  2. Generic callback:

    When programming Windows with C++, there is a chance that you will have to supply a C-style callback function to a Win32 API (like CreateThread, SetTimer, and so forth). If the callback function required has a LPVOID as an argument, everything is okay. Using the old trick passes this pointer as an LPVOID argument. But, unfortunately, there is an API called SetWinEventHook; its prototype is:
    HWINEVENTHOOK WINAPI SetWinEventHook(
    
    UINT eventMin,
                UINT
    eventMax,
    
    HMODULE hmodWinEventProc,
                WINEVENTPROC
    lpfnWinEventProc,
    
    DWORD idProcess,
                DWORD
    idThread,
    
    UINT dwflags)
    
    ;
    
    which takes a callback function, WINEVENTPROC. Its function signature is:
    VOID CALLBACK WinEventProc(HWINEVENTHOOK hWinEventHook,
    
    DWORD event,
    
    HWND hwnd,
                LONG
    idObject,
    
    LONG idChild,
    
    DWORD dwEventThread,
    
    DWORD dwmsEventTime);
    

    Obviously, there is no LPVOID parameter; hence, there is no way to pass this pointer. It is virtually impossible to access the non-static data member inside the WinEventProc function. That is horrible, and a nightmare for an OO developer.

    To solve this problem, just recall an old golden software development rule, "Many design problems can be solved by one more indirection." So, I created a template class named WinEvent. It has the following structure:

    template <typename WinEventHandler>
    class WinEvent
    {
      public:
      typedef VOID (CALLBACK WinEventHandler::*PfnCallback)
      (HWINEVENTHOOK,DWORD,HWND,LONG,LONG,DWORD,DWORD);
    
      static HWINEVENTHOOK InstallWinEventHook(
                           WinEventHandler *pHandler,
                           PfnCallback pfn,UINT eventMin,UINT
    eventMax,
                           HMODULE ModWinEventProc,DWORD
    idProcess
                           DWORD dThread,UINT dwFlags);
      private:
    
      static WinEventHandler *s_pHandler;
      static PfnCallback s_pfnCallback;
    
      private:
    
      WinEvent()
      ~WinEvent();
    
      static VOID CALLBACK WinEventProc(
                           HWINEVENTHOOK hWinEventHook,
                           DWORD event,HWND hwnd,LONG idObject,
                           LONG idChild,DWORD dwEventThread,
                           DWORD dwmsEventTime);
    };
    
    
    template <typename WinEventHandler>
    HWINEVENTHOOK
    WinEvent<WinEventHandler>::InstallWinEventHook(
          WinEventHandler *pHandler,
          PfnCallback pfn
          UINT eventMin,UINT eventMax,HMODULE hModWinEventProc,
          DWORD idProcess,DWORD idThread,UINT dwFlags)
    {
      s_pHandler=pHandler;
      s_pfnCallback=pfn;
      return SetWinEventHook(eventMin,eventMax,
                             hModeWinEventProc,WinEventProc,
                             idProcess,idThread,dwFlags);
    }
    
    
    
    
    template <typename WinEventHandler>
    VOID CALLBACK WinEvent<WinEventHandler>::WinEventProc(
      HWINEVENTHOOK hWinEventHook,
      DWORD event,
      HWND hwnd,
      LONG idObject,
      LONG idChild,
      DWORD dwEventThread,
      DWORD dwmsEventTime)
    {
      //delegate to the WinEventHandler class's function
      (s_pHandler->*s_pfnCallback)(hWinEventHook,event,hwnd,
                      idObject,aidChild,dwEventThread,
                      dwmsEventTime);
    }
    

    As mentioned above, SetWinEventHook takes only a C-style callback, so I made WinEventProc a static function, to access s_pHandler and s_pfnCallback inside the WinEventProc. I had no choice but made them static too, and now, if my MyDialog class wants to receive the callback, I need to add a member callback function. The new .h declaration becomes:

    class MyDialog
    {
    //same as the above
    private:
    VOID CALLBACK WinEventProc(
                   HWINEVENTHOOK hWinEventHook,DWORD event,HWND
                   hwnd,LONG idObject,LONG idChild,DWORD
                   dwEventThread,DWORD dwmsEventTime);
    };
    

    The function name doesn't need to be WinEventProc; it can be anything you like, as long as the function signature is correct. The following statement will call the InstallWinEventHook:

    WinEvent<CMyDialog>::InstallWinEventProc
                         (this,WinEventProc,,,,,);
    

Conclusion

Technically, the WinEvent class doesn't have to be a template. It can hold a "hardcoded" EventHandler type, but to reduce the de-coupling, a template is the only choice. I know the WinEvent class is unfinished and there is plenty of space to improve but the above design just shows the practical use of a template and I believe meta-programming has a growing impact in application development setting, too. Please enjoy.



Comments

  • Can't compile template member function

    Posted by Legacy on 11/29/2002 12:00am

    Originally posted by: mARK bLOORE

    I pasted the MyDialog code with the template function into 
    
    an MFC program generated by VisualC++ 6.0 Standard Edition,
    running under Win2K Pro. It failed to compile, with

    c:\...\templatetest.cpp(31) : fatal error C1001:
    INTERNAL COMPILER ERROR
    (compiler file 'msc1.cpp', line 1794)

    on the line
    ShowButtons(m_buttonArray,bShow);

    I have large amounts of redundant code due to the inability
    to use template functions that are members of a class. Can
    anyone tell me how to make that example compile?

    Reply
  • Give in Clear manner

    Posted by Legacy on 09/13/2002 12:00am

    Originally posted by: kumar

    I have a doubt in buttons i.e.,
    in Runtime, when mouse touch the button ,button never touch the mouse(button moves when mouse comes to it).

    Reply
  • Example 1 not so good

    Posted by Legacy on 09/01/2002 12:00am

    Originally posted by: Jan Vorlicek

    I like your example #2, but the first one is not so good example of using templates. For this case, inheritance would be much better. Just let both the buttons inherit from one base class, store pointers to these objects in the array with element type "pointer to the base class" and then just create the method you want to call for both (or more) types of objects virtual.
    
    In the case you have presented, it would be even easier, because the ShowWindow is independent of the object type and so no virtual method is needed. The code would look like this:

    #include<afxtempl.h>
    class MyDialog
    {
    //....
    private:
    CArray<CWnd *,CWnd *> m_buttonArray;
    };

    void MyDialog::ShowAllButtons(BOOL bShow)
    {
    int nIndex=0;
    for (nIndex=0;nIndex < m_buttonArray.GetSize();++nIndex)
    {
    m_buttonArray[nIndex]->ShowWindow(bShow);
    }
    }

    Maybe you think it is not so nice having pointers to objects instead of the objects itself as values, but in this case you don't have to take care about object deletion. You can obtain CWnd * to your buttons by calling GetDlgItem(ID). The MFC takes care about its proper deletion automatically.

    Jan

    Reply
  • CooL Idea

    Posted by Legacy on 08/31/2002 12:00am

    Originally posted by: HasuBibi

    It's realy a very good way to using Template. Thanks for showing the way!

    Reply
  • Never thought of that ...

    Posted by Legacy on 08/30/2002 12:00am

    Originally posted by: Darwen

    Well I never - what a neat solution to a really messy
    situation in the use of windows callbacks.

    I congratulate you sir ! It's not often I learn something so fundamentally useful. Many thanks for the posting.

    Darwen.

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

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds