Question

I'm in the process of porting a C++/WTL project from Visual Studio 2005 to VS 2008. One of the project configurations is a unit-testing build, which defines the preprocessor symbol UNIT_TEST.

In order to get my WTL classes into my test harness, I made a CFakeWindow class that stubs all the CWindow methods. Then in my stdafx.h file, I do this right below the import of atlwin.h (which defines the CWindow class):

#ifdef UNIT_TEST
#include "fakewindow.h"
#define CWindow CFakeWindow
#endif

My window classes then look like this:

class CAboutDialog : 
    public CDialogImpl< CAboutDialog, CWindow > 
    , public CDialogResize< CAboutDialog >
{
// class definition omitted...
};

This works great in VS 2005. The problem is that in VS 2008, the methods from the original CWindow class are getting called, instead of the CFakeWindow class. This of course causes the tests to fail, because CWindow is sprinkled with ATLASSERT(::IsWindow(m_hWnd)).

When I step through the code in the debugger, I see that the CAboutDialog class is inheriting from CDialogImpl<CAboutDialog, CFakeWindow>. Yet when I call a method on CAboutDialog (e.g. EndDialog(code)), the CWindow method is getting called.

Is this a bug in VS 2008, or was my conditional template inheritance technique an abomination that VS 2005 allowed but VS 2008 "fixed"? Is there a work-around, or do I need to consider a different technique to unit-test WTL classes? I really like this technique, because it lets me get WTL classes into a test harness without mucking about with the WTL library.

Edit: As noted in the response to Conal, below, the preprocessor output shows that my class is inheriting from CFakeWindow:

class CAboutDialog :
    public CDialogImpl<CAboutDialog, CFakeWindow >
    , public CDialogResize< CAboutDialog >
    ...

And as stated above, when I step through the code in the debugger, CAboutDialog is shown in the locals window as inheriting from CFakeWindow.

Edit 2: As per Conal's advice, I stepped through the disassembly, and the code is supposedly calling the CFakeWindow method, but the CWindow method is what is actually called.

        if ( wID == IDCANCEL ) 
00434898  movzx       edx,word ptr [ebp+8] 
0043489C  cmp         edx,2 
0043489F  jne         CAboutDialog::OnCloseCmd+90h (4348B0h) 
        {
            EndDialog( wID ) ;
004348A1  movzx       eax,word ptr [ebp+8] 
004348A5  push        eax  
004348A6  mov         ecx,dword ptr [ebp-10h] 
004348A9  call        ATL::CDialogImpl<CAboutDialog,ATL::CFakeWindow>::EndDialog (40D102h) 
        }
        else
004348AE  jmp         CAboutDialog::OnCloseCmd+9Ah (4348BAh) 
        {
            EndDialog(IDOK);
004348B0  push        1    
004348B2  mov         ecx,dword ptr [ebp-10h] 
004348B5  call        ATL::CDialogImpl<CAboutDialog,ATL::CFakeWindow>::EndDialog (40D102h) 

I'm starting to lean more toward a bug in the VC++ 2008 debugger.

Was it helpful?

Solution

This might be a clearer way of handling it:

#ifdef UNIT_TEST
#include "fakewindow.h"
#define TWindow CFakeWindow
#else
#define TWindow CWindow
#endif

Perhaps there's a case where the redefine is not getting through the precompiled headers. If so, this will catch any such problem.

OTHER TIPS

I think this is a long shot, but is it possible that precompiled headers are playing havoc with your build? Try turning them off in your project (if they're on) and do a clean build to see if you get any better behavior.

Have you looked at the preprocessor output to confirm that your hack is in fact doing what you think? Using the preprocessor to rename classes that are part of a template library is fraught with danger. Also check that the signature of each and every method in your CFakeWindow exactly matches CWindow.

VS2008 has been around for quite a long time now, so I would not be considering a compiler bug until you have first eliminated all other possibilities.

Try something like:

class CAboutDialog :
    public CDialogImpl<CAboutDialog, FAKE_CWindow_MACRO >

That way you can stop trying to use the preprocessor to rewrite system header files, and use it for your own code.

Yes, I know you have to "refactor" your code, but it's a search-and-replace job.

Your current technique is something of a hack because it makes an unwarranted assumption: that no source file will #include any header from ATL that relies on a consistent definition of CWindow. This is not likely to be stable as code changes over time.

Then in my stdafx.h file, I do this

AFAIK, stdafx.h is a precompiled header by default. So when you define UNIT_TEST macro, it doesn't affect precompiled file, which still has CWindow as template argument.

I see the library symbols are in the namespace ATL. Is that perhaps throwing off your macro trickery?

In general, I would not count on any debugger to report template instantiations perfectly. Too many weird optimizations are required to happen even in Debug builds.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top