July 1995

Microsoft Systems Journal Homepage

Born Again Templates

I've never been a big fan of C++ templates. I just never had a serious need for them. Sure, they're great for building class libraries of little parameterized classes like lists and arrays, but I don't write that stuff. I buy it. But the Template God hath revealed Itself unto me and I have seen the light. Hallelujah!
To build CMDLEARN, I needed to trace all calls to WindowProc, OnCommand, and OnCmdMsg. There are a number of ways I could've done it. One is to implement an API hook. That's too drastic and requires mucking around in assembly language—yuck! Another way is to add tracing hooks to CWnd and CCmdTarget, but I didn't want to modify MFC source code. Besides, many of the derived class overrides return before reaching CWnd and CCmdTarget, so I wouldn't catch all the calls. What I really wanted to do was override WindowProc, OnCommand, and OnCmdMsg for every window class and command target in CMDLEARN. That meant writing a bunch of classes: CTraceApp, CTraceFrameWnd, CTraceView, CTraceDoc, CTraceMDIFrame, and so on. The prospect of writing essentially the same code for half a dozen classes was hardly appealing.
That's where templates saved my soul. Instead of writing all those classes, I wrote just two template classes, TrCmdTarget<Type> and TrWnd<Type>. TrCmdTarget overrides OnCmdMsg for CCmdTargets; TrWnd overrides WindowProc and OnCommand for CWnds (see TRACEMSG.H). Type is whatever class you want to base the tracing version on. Here's how to trace CFrameWnd:

 class CMainFrame : public TrWnd<CFrameWnd> {
 •
 •
 •
 };
The compiler sees TrWnd<CFrameWnd> and generates two new classes:

 class TrCmdTarget<CFrameWnd> : public CFrameWnd {
 public:
    virtual BOOL OnCmdMsg(UINT nID, int nCode, 
                         void* pExtra,
       AFX_CMDHANDLERINFO* pHandlerInfo);
 };

 class TrWnd<CFrameWnd> : public TrCmdTarget<CFrameWnd> {
 public:
    virtual LRESULT WindowProc(UINT msg, WPARAM wParam, LPARAM lParam);
    virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
 };
TrWnd overrides WindowProc and OnCommand for windows; TrCmdTarget overrides OnCmdMsg for command targets. I wrote TrCmdTarget so you can override a nonwindow command target like CDocument. The templates add a layer of base classes between CMDLEARN and MFC. To make switching between the tracing and nontracing classes easy, I used #define symbols to provide a layer of indirection.

 #ifdef _DEBUG
 #define TWinApp      TrCmdTarget<CWinApp>
 #define TDocument    TrCmdTarget<CDocument>
 #define TMDIFrameWnd TrWnd<CMDIFrameWnd>
 •
 •
 •
 #else 
 #define TWinApp      CWinApp
 #define TDocument    CDocument
 #define TMDIFrameWnd CMDIFrameWnd
 
 •
 •
 •
 #endif
This has the added benefit of making your program look like OWL code.
If a TClass is used anywhere, the compiler generates the whole shebang, so CMDLEARN.EXE actually contains a half-dozen or so classes, exactly as if I'd written them by hand. With all that generated code, it's a good idea to keep the templates as small as possible, so the template classes all call a global CMsgTracer object called theTracer to actually do the tracing. CTracer writes messages to TRACE.OUT, keeping track of call levels to indent the diagnostics all nice and pretty.
Before you marvel at how clever I am and run to graft my tracing code onto your own app, there are a few disgusting aspects you should be aware of. First of all, the template classes are not part of the MFC class hierarchy because the DECLARE/IMPLEMENT_DYNAMIC macros have a major brain hemorrhage when you pass them template names. So MFC doesn't know these classes exist. MFC thinks CApp is derived from CWinApp even though TrCmdTarget<WinApp> sits between. Likewise, the message map base pointers skip the template classes. So, for example, the base class message map pointer for CFileView points to CView's message map, not TrWnd<CView>. That's why I call the template classes "phantom classes." (Also because it sounds cool.) Fortunately, their ghostliness is harmless for most purposes.
Another problem is that if any "real" app class overrides WindowProc, OnCommand, or OnCmdMsg, and returns before calling its base class (TrWnd<Base>), the call won't be traced.
Finally, my tracing mechanism has a major drawback as a debugging tool: it requires that you modify your source code. You must derive all your clases from the TClasses instead of the normal MFC classes. Nevertheless, if you're experiencing especially difficult command routing woes, it might be worth your while. If so, you can compile your app with TRACEMSG.CPP. Its output is in some ways better than Spy's: you get symbolic names for MFC messages and command IDs, tracing for OnCommand and OnCmdMsg (OnNotify would be easy to add), and you can trace nonwindow objects like documents and your app.
Of course, it only works with the 32-bit Visual C++ compiler. That's the only one from Microsoft that supports templates.


©1995 Microsoft Corporation. All rights reserved.
Terms of Use
.