Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > July 1999
July 1999

Microsoft Systems Journal Homepage

C++ Q&A

Paul DiLascia is the author of Windows ++: Writing Reusable Code in C++ (Addison-Wesley, 1992) and a freelance consultant and writer-at-large. He can be reached at askpd@pobox.com or http://pobox.com/~askpd.

Q I can use two approaches for throwing an exception. The first approach is

 CMyException myException;
     throw myException;  
and the second is:

 CMyException* pMyException = new CMyException();   
     throw pMyException;  
As far as I can figure out, the disadvantage of the first method is that the object is first constructed on the stack and then copied again to be passed from throw. The thing that I don't understand about the second approach is, who is responsible for freeing the CMyException object? MFC always uses the second approach, but none of the sample code that shows catching MFC exceptions has code to free the exception object. So, who's freeing it?

A Good question. The answer may surprise you. But first, let me give you the quick lowdown on exceptions, borrowed from Scott Meyers' excellent book, More Effective C++ (Addison-Wesley, 1995), Item #13. There are three ways you can throw/catch exceptions: by pointer, by value, or by reference.
If you throw/catch by pointer, you must allocate the exception object to throw, then delete it when you catch. This approach involves no extra copying, but there's a problem. If the exception was thrown using


 static CMyException me(...);
 throw &me;
your program will [insert bad turn of events here] if the code that catches the exception tries to delete it. It may be fairly unusual to throw a static exception object, but it can be useful sometimes. The point is, there's no way the code that catches the exception can know whether the object was allocated from the heap or static memory, so there's no way for it to be absolutely sure whether to delete it. And it goes without saying that you can't throw a stack object using this method:

 CMyException me(...); 
 throw &me;  
Everyone knows that a stack object no longer exists when control leaves the function containing it.
The second approach for throwing and catching exceptions is to throw/catch by value.

 // throw code
 CMyException me;
 throw me;  
 
 // catch code 
 catch (CException e) {
     // handle it
 }  
This avoids the allocation/destruction problem since the object is passed on the stack as a value, but at the cost of copying the object twice—once when the exception is thrown, and again when the exception is caught. You can avoid the first copy by writing

 throw CMyException(...);  
which creates the CMyException directly on the return stack, but the second copy is still unavoidable.
Personally, I think the copy issue is no big deal since exceptions are by definition supposed to be, well, exceptional—that is, they don't happen often. For most applications, the time required to copy a few bytes is, to understate the case, insignificant. But there's a more serious problem with catching by value: it's called the slicing problem. I'm not talking about Ginsu knives, I'm talking about slicing objects.
In the previous code, e is declared as type CException, so that's what you get. Always. When the exception-throwing code throws a CMyException (derived from CException), the code that catches the exception gets a CException. The object loses all member data and functions added by CMyException; this information is sliced off. In general, C++ polymorphism only works through a pointer to an object, not an object itself. So in the case of MFC, if the code that catches the exception attempts to call the virtual functions CException::GetErrorMessage or ReportError, the compiler generates a call to CExeption::GetErrorMessage, not CMyException::GetErrorMessage—definitely not what you want.
The only way to fix this would be to declare e as CMyException.

 catch (CMyException e) {
     // handle it 
 }  
But this is obviously no solution; what happens when you introduce CMyException2? You have to modify every place that catches exceptions of this type, which rather defeats the whole purpose of polymorphism. For this reason, throwing exceptions by value is exceptionally grody.
As you may have guessed (you're so shrewd), I've saved the best option for last: throw/catch by reference. Using references, you can still throw the exception by value

 throw CMyException(...);  
but now you use a reference to catch it:

 catch (CException& e) {
     // handle it 
 }  
Instead of copying the thrown object into a local variable, the compiler lets the code catching the exception manipulate the stack copy directly. There's no slicing problem. Throwing/catching exceptions by reference has the best of all worlds: no extra copying, you don't have to worry about who destroys the exception object (since it's always passed on the stack), and polymorphism is preserved. So from a purely theoretical perspective, references are the best way to do exceptions.
So much for theory. Now let's enter the world of MFC. MFC has its own exception mechanism that's based on C++, looks like C++, but isn't C++. Here you find TRY, END_TRY, CATCH, CATCH_ALL, AND_CATCH, END_CATCH, and other macros that resemble the C++ keywords.
Let's take a look at a vanilla TRY/CATCH sequence in MFC:

 TRY
     DoSomething();
 CATCH (CMyException, e)
     HandleBadKarma();
 END_CATCH  
How does the compiler expand this? Don't bother going to the header files; I'll show you right now.

 // TRY expands to:
 { AFX_EXCEPTION_LINK _afxExceptionLink; try {
     DoSomething();
 // CATCH expands to:
 } catch (CMyException* e)
     { ASSERT_KINDOF(CMyException, e);
       _afxExceptionLink.m_pException = e;
        HandleBadKarma();
 // END_CATCH expands to:
    }
 }  
The first thing you'll notice is that MFC always catches exceptions by pointer (and throws them that way too, naturally). But what's that strange AFX_EXCEPTION_ LINK object, _afxExceptionLink? It's a little object that holds a pointer to the exception. When MFC catches the exception, it sets _afxExceptionLink.m_pException to point to the caught exception. And, as you might have guessed, the destructor AFX_EXCEPTION_LINK::~AFX_EXCEPTION_LINK deletes the object. It doesn't do it directly, but calls another function, AfxTryCleanup:

 // called from ~AFX_EXCEPTION_LINK
 void AFXAPI AfxTryCleanup()
 {
     AFX_EXCEPTION_CONTEXT* pContext
         = AfxGetExceptionContext();
     AFX_EXCEPTION_LINK* pLinkTop
         = pContext->m_pLinkTop;  
 
    // delete current exception
     if (pLinkTop->m_pException != NULL)
         pLinkTop->m_pException->Delete();  
 
    // remove ourself from the top of the chain
     pContext->m_pLinkTop = pLinkTop->m_pLinkPrev; 
 }  
The linked list of AFX_EXCEPTION_LINKs forms a stack. One global AFX_EXCEPTION_CONTEXT holds the pointer to the AFX_EXCEPTION_LINK on the top of the stack (m_pLinkTop). The AFX_EXCEPTION_LINK constructor adds itself to the stack; the destructor removes itself and deletes the exception object by calling m_pException->Delete. The upshot is this: MFC exceptions delete themselves as long as you use CATCH. Contrary to what you might expect, CException::Delete is not a virtual function; it's a normal member function that calls delete.

 void CException::Delete()
 {
     if (m_bAutoDelete > 0)
        delete this; 
 }  
By default, exceptions are auto-deleting; that is, m_bAutoDelete is TRUE. If you don't want the catch code to delete your exception—for example, if it's a static object as in the previous examples—you can change m_bAutoDelete.

 static CMyException staticex();  


 staticex.m_bAutoDelete = FALSE;
 throw &staticex;  
Now CATCH code all over the world will not delete staticex. You can set m_bAutoDelete for a class or for an individual object, but you're forever locked into using the MFC CATCH, not the C++ catch. I won't bother to explain all the varieties of CATCH_ALL, AND_CATCH, THROW_LAST, and so on, since once you understand the idea of using AFX_EXCEPTION_LINK, you can pretty well figure out how they work.
Why does MFC do all this magic? Mostly for historical reasons. The Microsoft
® compiler was slow to support C++ exceptions using try/catch, so MFC provided its roll-yer-own implementation using TRY/CATCH, which was originally implemented using C setjmp/longjmp. For backward compatibility, you can still use this implementation by #defining the symbol _AFX_OLD_EXCEPTIONS before #including <afx.h>.
By now I've answered your question. The MFC sample code doesn't delete the exceptions because the TRY/CATCH code is so clever it generates code to delete them for you. But if you're reading the source code for MFC itself, you may be confused by code like this:

 TRY {
     DoSomething(); 
 } CATCH_ALL(e) {
     // handle it
     DELETE_EXCEPTION(e); 
 }  
What's DELETE_EXCEPTION? I can't explain any better than the MFC source code itself, which contains the following comment in stdafx.h: "MFC does not rely on auto-delete semantics of the TRY/CATCH macros, therefore those macros are mapped to something closer to the native C++ exception handling mechanism when building MFC itself."
The stdafx.h used to build MFC contains its own entirely different definitions for TRY, CATCH, and so on; ones that map more or less directly to try/catch (with some ASSERTs thrown in for good measure). Since these internal TRY/CATCH implementations don't have the magic delete code, DELETE_EXCEPTION is required to manually call e->Delete.
So there are three possible implementations of TRY/CATCH: the "normal" implementation you get without doing anything (with all the magic auto-delete stuff using AFX_EXCEPTION_LINK); the "old" backward-compatible setjmp/longjmp implementation you get when you #define _AFX_OLD_EXCEPTIONS; and the implementation MFC uses internally, which requires DELETE_EXCEPTION to delete the exceptions. If all this makes you want to throw up your hands and pull out your hair, I can sympathize. It would be nice if the folks in Redmond rationalized their exception handling to map TRY/CATCH directly to try/catch, but I don't think it will happen since that might break lots of existing code.
So what should you do? If you call an MFC function that can throw an exception, you have no choice but to try to catch it. If you have old code that already uses TRY/CATCH, it probably works fine and you shouldn't do anything. But if you're writing new code, I would avoid TRY/CATCH entirely and stick with try/catch. Why? Three reasons: first, try/catch produces smaller code than the MFC macros (no hidden local objects with constructor/destructor calls); second, CATCH limits you to catching CException-derived exceptions; and finally, it just plain looks better to use real C++. TRY/CATCH was always a temporary kludge, so the sooner you forget it, the better. The only catch is, if you use try/catch, you have to remember to delete your exception object!

 try{
   CallSomeMFCFunction();
 } catch( CException*e) {
 // recover
 e->Delete();
 }
Q Since MFC is so full of macros, it's sometimes hard to know how the code looks to the compiler. Is there a way to see how my code looks after the preprocessor passed over it (the actual, expanded code the compiler works on)?
Gil Sudai

A Yes, there is. You can use the compiler's /P switch, which tells the compiler to preprocess your source code to a file (/E preprocesses to stdout). As a test, I went to one of my projects and used /P to generate a preprocessor listing for mainfrm.cpp by typing the following line at the command prompt:


 cl /P mainfrm.cpp  
The compiler generated a new file, mainfrm.i, which contained the preprocessed code. Be warned, however: this file is huge! My 3109-byte mainfrm.cpp generated a preprocessed file that's over 1MB! The preprocessor expands literally everything: all the #include files such as winuser.h and afxwin.h, and all the files they include, and so on.
For example, at line 40,865 of my preprocessed file, the following appears:

 #line 5956 "d:\\MSDEV\\SDK\\include\\winuser.h"  
 __declspec(dllimport)
 BOOL 
 __stdcall 
 SetWindowTextA(
    HWND hWnd,
    LPCSTR lpString); 
 __declspec(dllimport) 
This comes from the winuser.h lines:

 WINUSERAPI 
 BOOL 
 WINAPI 
 SetWindowTextA(
    HWND hWnd,
    LPCSTR lpString);  
In the preprocessed version, WINUSERAPI and WINAPI are expanded to their macro definitions; BOOL, HWND and LPCSTR are not expanded because these are typedefs, not macros.
The preprocessed file is so huge you have to go to the end to find your own source code. But once you do, you can see how MFC expands various macros. For example, the familiar lines

 IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd) 
 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
     ON_WM_CREATE() 
 END_MESSAGE_MAP()  
expand in mainfrm.i to the code in Figure 1, and the even more ubiquitous

 TRACE0(_T("Failed to create toolbar\n"));  
gets preprocessed to:

 ::AfxTrace("%s", "Failed to create toolbar\n");  
The output from /P doesn't contain your code comments, which can make it difficult to work with. If you want to find a particular spot quickly, you can add a dummy variable with an unusual name to search for, placing it just before the macro you want to investigate. For example:

 int searchForThisName foo;
 MACROTOLOOKAT(...);  
But if you find yourself confused by the MFC macros—and who doesn't from time to time?—then /P is a good way to see what's going on. Typing "cl /P" at the command prompt is a quick way to generate the preprocessed code, but if you want to be absolutely sure you get all the #defines right (for example, _DEBUG, _AFXDLL, and so on), you should add the /P to your Project Settings, as in Figure 2.
Figure 2 Preprocessing Source Code to a File
Figure 2 Preprocessing Source Code to a File

Q I've implemented a custom File Open dialog like the one in your January 1998 column, but I'm having trouble with the Tab key. I can tab quite happily around the common dialog, but not onto my new controls. If I use the mouse to set focus I can tab around my new controls, but Tab never "jumps the gap," so to speak. This is probably because they're two different dialogs next to each other. How do I get the Tab key to move onto the controls that have been added to the common dialog?
Michael Dye

A It's easy: make sure you set the WS_EX_CONTROLPARENT style in your child dialog. This style does exactly what you want: it lets the user navigate among the child controls using the Tab key. Isn't it amazing sometimes how Windows® has a trick to do exactly what you want? (In fact, Michael solved his own problem and emailed me the answer before I figured it out—but I thought this was a good enough tip to pass on.)

Update

In the March 1999 issue, a reader asked about a mysterious bug he was experiencing in release builds only. The problem had to do with ON_MESSAGE, which lets you handle an arbitrary message such as WM_MYMESSAGE.


 const WM_FOO = WM_USER + 1; 
 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
     ON_MESSAGE(WM_FOO, OnFoo) 
 END_MESSAGE_MAP  
The handler function OnFoo must have the signature

 LRESULT OnFoo(WPARAM, LPARAM)
That is, it should take a WPARAM and LPARAM, and return an LRESULT. If instead OnFoo has no arguments and returns void (an easy error if you don't use the arguments), your code will compile and even run—in debug builds. But because of the differences in stack checking and correction, it fails in release builds—in an especially gruesome and hard-to-understand way. (The stack becomes corrupt, the program goes on executing a while, then crashes somewhere else far removed from the real bug.)
Many readers wrote letters of commiseration saying they had the same problem, and were wondering if there's some way to protect themselves from this easy error. In fact, there is.
ON_MESSAGE is defined in afxmsg_.h like so:

 #define ON_MESSAGE(message, mbrFn) \
    { message, 0, 0, 0, AfxSig_lwl, \
     (AFX_PMSG)(AFX_PMSGW)(LRESULT \
     (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM))&mbrFn },  
The problem is that ON_MESSAGE casts its function argument (mbrFn) to the desired type, a CWnd function that takes WPARAM, LPARAM and returns LRESULT. The compiler silently obeys the cast regardless of whatever type mbrFn actually is.
A safer way to write the macro is to use static_cast:

 #define ON_MESSAGE(message, mbrFn) \
    { message, 0, 0, 0, AfxSig_lwl, \
     (AFX_PMSG)(AFX_PMSGW) static_cast<LRESULT \
     (AFX_MSG_CALL CWnd::*)(WPARAM, LPARAM)>&mbrFn },  
static_cast is more strict about the conversions it allows. Unlike the old C cast, it won't convert function pointers unless it can convert all the arguments and return types. If ON_MESSAGE were written as it was in the previous code, and you tried to use a void OnFoo handler, the compiler would complain:

 'static_cast' : cannot convert from 'void
 (CMainFrame::*)(void)' to 'long (CWnd::*)(unsigned int,long)'
The message even goes on to advise you to use reinterpret_cast if you really, really want to perform this ugliness.
There's just one little problem here. ON_MESSAGE is in afxmsg_.h, which belongs to MFC. So what are you going to do? You could ask Microsoft to rewrite afxmsg_.h to use static_cast (hint, hint), but even if your letter were received with enthusiasm, you'd have to wait for the next release.
One reader told me his group rewrote afxmsg_.h to use static_cast. I can't condone this approach because I don't like modifying third-party source code. In fact, the first thing I do when I install a new release of MFC or the SDK or some other foreign code is a global attribute change ATTRIB +R *.* on all the files, to make them read-only so I can't even edit them by accident.
Besides, there's no need to change afxmsg_.h! You can accomplish the same thing using your own mymsg.h file. This file would contain the following lines:

 #undef  ON_MESSAGE 
 #define ON_MESSAGE \
     // static_cast version...   
Now all you have to do is #include mymsg.h after <afxwin.h> in your project's stdafx.h. MFC uses the same approach itself to redefine TRY, CATCH, CATCH_ALL, and so on (see the first question in this column) in its stdafx.h. It's up to you whether you want to redefine all the ON_XXX macros or just ON_MESSAGE. The other macros usually don't cause a problem because you generate the function from the Class Wizard or—like me—by copying and pasting the declaration from afxwin.h. But even ON_WM_CREATE can go awry if your OnCreate handler has the wrong signature because all the ON_XXX macros use the old C-style cast. So if you want to be really, really safe, you should redefine all the macros in afxmsg_.h. An editor macro will do the job in a few seconds.
I forgot to mention in the March 1999 column that there's another macro, ON_MESSAGE_VOID, you can use when the handler function doesn't use its arguments. This macro is defined in afxpriv.h, which is a private MFC include file, though many programs include it.

 #define ON_MESSAGE_VOID(message, memberFxn) \
     { message, 0, 0, 0, AfxSig_vv, \
         (AFX_PMSG)(AFX_PMSGW) \
   (void (AFX_MSG_CALL CWnd::*)(void))&memberFxn },  
ON_MESSAGE_VOID uses the signature code AfxSig_vv (void function returning void) so the dispatch code calls the handler function with no arguments. This avoids the argument mismatch bug, since no arguments are left on the stack. The only problem with ON_MESSAGE_VOID is that you have to remember to use it! In any case, whether you use ON_MESSAGE or ON_MESSAGE_VOID, both macros can be made type-safe by using static_cast.
In general, you should always use static_cast, dynamic_ cast, and const_cast. As the ON_MESSAGE example shows, static_cast is an especially good idea when casting function pointers. Avoid reinterpret_cast like the plague—but even that's better than the parenthetic C-style cast, for the simple reason that it announces itself more loudly as a potential bug.

Have a question about programming in C or C++? Send it to Paul DiLascia at askpd@pobox.com

From the July 1999 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.


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

© 2016 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft