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.
- 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. - 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.