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.