Robert Schmidt
June 3, 1999
In this column, Robert Schmidt discusses Microsoft's extensions to standard exception handling: Structured Exception Handling (SEH) and Microsoft Foundation Class (MFC) exception handling.
In my previous column, I surveyed the taxonomy of exceptions and the Standard C Library's support for managing them. In this column I discuss Microsoft's extensions to those standard mechanisms: Structured Exception Handling (SEH) and Microsoft Foundation Class (MFC) exception handling. While SEH is available in both C and C++, the MFC mechanism—like MFC in general—is available only in C++.
(One slightly off-topic note: Starting with my next column, Deep C++ will be published on the first and third Thursdays of each month. As a result, each column will be roughly half as long as this one, although the cumulative depth and breadth should stay unchanged.)
Structured Exception Handling is really a set of Windows® services available to programs in any language. With Visual C++®, Microsoft packages and abstracts these services via non-standard keywords and library routines. Other vendors of Windows-targeted translators may choose different ways to achieve similar results. Within the context of this column series, the terms "Structured Exception Handling" and "SEH" refer to Visual C++'s presentation of the underlying Windows exception services.
To support SEH, Microsoft extends the C and C++ languages with four new keywords:
Because these keywords are non-standard language extensions, you must compile with such extensions enabled (/Fa option off).
Why do the keywords have underscores? As the C++ Standard (subclause 17.4.3.1.2, "Global names") tells us:
Certain sets of names and function signatures are always reserved to the [translator or compiler] implementation:
The C Standard has similar wording for C implementations.
Since the SEH keywords conform to the above rules, Microsoft is within its rights to use them. By implication, you are not allowed to use reserved names in portable programs. This means you must avoid defining identifiers with names such as __MYHEADER_H__ or _FatalError.
Interestingly and unfortunately, the Visual C++ application wizards can generate source code usurping reserved identifiers. For example, if you create a new service with the ATL COM AppWizard, the resulting skeleton code defines names like _Handler and _twinMain—reserved names the Standards say your programs can't portably use.
To mitigate this non-conforming behavior, you can, of course, manually change the names in the Wizard-generated source. Fortunately, many of the suspect names are private class members, inaccessible to code outside the defining class; globally replacing those names in the affected .h and .cpp files should suffice. Unfortunately, one function (_twinMain) and one object (_Module) are declared extern, meaning other parts of the same program may assume these names. (Indeed, the Visual C++ library libc.lib requires the name _twinMain to be available at link time.)
I recommend you leave the Wizard-generated names alone, but otherwise don't define such non-portable names in your code. Furthermore, you should document all non-portable definitions as an aid to program maintenance; remember, future versions of Visual C++ (and current versions of other C++ translators) may use those same names in other ways, thereby breaking your code.
Microsoft also defines several SEH identifiers in the non-Standard header excpt.h, which is included by windows.h. Beyond those for internal housekeeping, definitions of note include:
With these macros, Microsoft has done something that drives me batty: They've defined multiple aliases to the same function. For example, excpt.h has these declarations and definitions:
unsigned long __cdecl _exception_code(void);
#define GetExceptionCode _exception_code
#define exception_code _exception_code
This means you have three ways of calling the same function. Which alias do you choose? And is that the same alias maintainers are expecting?
In its documentation, Microsoft seems to favor GetExceptionCode, a name matching the form of most other global Windows API functions. My search of the MSDN Online Library found 33 mentions of GetExceptionCode, compared to only 2 for _exception_code and zero for exception_code. I will follow Microsoft's lead, recommending GetExceptionCode and its similarly named brethren.
Because two of the _exception_code aliases are macros, you can't reuse the same names. I ran into this problem while developing my code samples for this column. I defined a local object named exception_code—or so I thought. In reality, I was defining a local object named _exception_code, because that's what my (inadvertent) use of the macro exception_code expanded to. The solution, once I reckoned the problem, was simply to change my object's name from exception_code to code.
Finally, excpt.h defines one particular macro—try—that also happens to be a genuine keyword in C++. This means you cannot easily mix SEH and Standard C++ exception blocks in a translation unit that includes excpt.h—unless you are willing to #undef the try macro. While such un-defining exposes the real try keyword, it also risks confusing SEH-savvy maintainers. On the other hand, programmers versed in Standard C++ will expect try to be a keyword, not a macro.
I believe that including a header—even a non-standard one like excpt.h—should not alter the behavior of code that otherwise strictly conforms to the standard. I also strongly believe that masking or redefining standard-defined keywords is simply bad practice. My advice: #undef try, don't use the other pseudo-keyword macros, but do use the real keywords (like __try).
The primary "structured" syntactic element within Structured Exception Handling is the try block. Such a block has the following basic form:
__try compound-statement handler
where a handler is either:
__except ( filter-expression ) compound-statement
or:
__finally compound-statement
More graphically, a given try block must look like either:
__try
{
...
}
__except(filter-expression)
{
...
}
or:
__try
{
...
}
__finally
{
...
}
Inside the __try portion of a try block, you may use a leave statement:
__try
{
...
__leave;
...
}
Within larger syntactic contexts, an entire try block is considered a single statement, so that:
if (x)
{
__try
{
...
}
__finally
{
...
}
}
is equivalent to:
if (x)
__try
{
...
}
__finally
{
...
}
Other notes:
In my last column I enumerated five stages of an exception's lifetime. Within the realm of Structured Exception Handling, those stages are implemented thusly:
A simple example:
int filter(void)
{
/* Stage 4 */
}
int main(void)
{
__try
{
if (some_error) /* Stage 1 */
RaiseException(...); /* Stage 2 */
/* Stage 5 of resuming exception */
}
__except(filter()) /* Stage 3 */
{
/* Stage 5 of terminating exception */
}
return 0;
}
Microsoft calls handlers defined with __except exception handlers, and those defined with __finally termination handlers.
Once an exception is raised, exception handlers—those starting with __except—are interrogated from the point of the exception outward, up the function-call chain. As each exception handler is discovered, its filter expression is evaluated. What happens after each filter evaluation depends on that filter's resulting value.
excpt.h defines macros for three filter values, all of type int:
Earlier in this column, I said that filter expressions should be compatible with type int, so that they match the three macro values. That advice may have been too conservative: My experience shows that Visual C++ accepts filter expressions of all integral types, pointer types, structure types, array types—even the void type! (However, my attempts at floating-point filters were greeted with Internal Compiler errors.)
Further, all filter values appear to be valid, at least for integral types. Non-zero values with sign bit off act like EXCEPTION_EXECUTE_HANDLER, while values with sign bit on act like EXCEPTION_CONTINUE_EXECUTION. Perhaps all that matters is the value's underlying bit pattern.
If an exception handler's filter evaluates to EXCEPTION_CONTINUE_SEARCH, the handler effectively refuses to catch the exception, and the search continues for another exception handler.
Once a filter catches an exception—by yielding a value other than EXCEPTION_CONTINUE_SEARCH—the program recovers. How that recovery occurs depends again on the filter value:
As their name suggests, termination handlers—those starting with __finally—are called in the wake of a terminating exception. In their role as clean-up code, these handlers are analogous to the Standard C Library's atexit handlers and C++ destructors. Such analogy is only partial, however, since termination handlers can also be entered through normal execution flow, just like non-handler code. Exception handlers, in contrast, always behave as true handlers: They are entered if and only if their corresponding filters evaluate to EXCEPTION_EXECUTE_HANDLER.
Termination handlers don't intrinsically know if they've been entered through such normal flow, or because a try block was terminated abnormally. To determine why they were entered, handlers can call AbnormalTermination. This routine returns an int, where zero means the handler was entered as part of normal execution flow, while any other value means the handler was entered via abnormal termination.
AbnormalTermination is actually a macro aliased to _abnormal_termination. Interestingly, Visual C++ treats _abnormal_termination as a context-sensitive intrinsic function, almost like a keyword. You cannot call that function from just anywhere; you must call it from within a termination handler. Unfortunately, this means you cannot have the handler call an intermediate function, which in turn calls _abnormal_termination; such attempts result in a compile-time error.
The following C example demonstrates the interactions among different filter values and handler types. The first version is a small complete program; each subsequent version is a slight variation on its predecessor. All versions are self-annotating, so that you can see the control flow and behavior.
The program raises an exception object via RaiseException. That function's first parameter is an exception code, typed as a 32-bit unsigned integer (DWORD); Microsoft has reserved exception codes in the range [0xE0000000, 0xEFFFFFFF] to represent user-defined errors. All other RaiseException parameters accept 0 for normal usage.
The exception filters I use here are fairly primitive. In the "real world," your filters would probably call GetExceptionCode and GetExceptionInformation to query the raised exception object's properties.
Within Visual C++, create an empty Win32 console application project named SEH_test, with all default settings. Add the following C source file to that project:
#include <stdio.h>
#include "windows.h"
#define filter(level, status) \
( \
printf("%s:%*sfilter => %s\n", \
#level, (int) (2 * (level)), "", #status), \
(status) \
)
#define termination_trace(level) \
printf("%s:%*shandling %snormal termination\n", \
#level, (int) (2 * (level)), "", \
AbnormalTermination() ? "ab" : "")
static void trace(int level, char const *message)
{
printf("%d:%*s%s\n", level, 2 * level, "", message);
}
extern int main(void)
{
DWORD const code = 0xE0000001;
trace(0, "before first try");
__try
{
trace(1, "try");
__try
{
trace(2, "try");
__try
{
trace(3, "try");
__try
{
trace(4, "try");
trace(4, "raising exception");
RaiseException(code, 0, 0, 0);
trace(4, "after exception");
}
__finally
{
termination_trace(4);
}
end_4:
trace(3, "continuation");
}
__except(filter(3, EXCEPTION_CONTINUE_SEARCH))
{
trace(3, "handling exception");
}
trace(2, "continuation");
}
__finally
{
termination_trace(2);
}
trace(1, "continuation");
}
__except(filter(1, EXCEPTION_EXECUTE_HANDLER))
{
trace(1, "handling exception");
}
trace(0, "continuation");
return 0;
}
Now build the code. (You might get a warning about the unused label end_4; if so, ignore the warning for now.)
Implementation notes:
When you run this program, you should see the following output:
0:before first try
1: try
2: try
3: try
4: try
4: raising exception
3: filter => EXCEPTION_CONTINUE_SEARCH
1: filter => EXCEPTION_EXECUTE_HANDLER
4: handling abnormal termination
2: handling abnormal termination
1: handling exception
0:continuation
The chain of events:
Note that control passes backward through the same levels twice: first to evaluate exception filters, then again to unwind the stack and run termination handlers. This presents opportunities for mischief if an exception filter modifies something in a way that a termination handler doesn't expect. As a rule, then, you should avoid generating side effects in your filters; if you must have side effects, save them for your termination handlers.
In the example program, change the line:
__except(filter(1, EXCEPTION_EXECUTE_HANDLER))
to:
__except(filter(1, EXCEPTION_CONTINUE_SEARCH))
so that the no exception filter catches the exception. Upon running the modified program, you should see the following:
0:before first try
1: try
2: try
3: try
4: try
4: raising exception
3: filter => EXCEPTION_CONTINUE_SEARCH
1: filter => EXCEPTION_CONTINUE_SEARCH
...followed by the dialog box shown here.
Figure 1. The User Exception dialog box
Clicking Details>> reveals the following.
Figure 2. Details of the User Exception dialog box
In this error message, SEH_TEST is the offending program and e0000001H is the exception code originally passed to RaiseException.
The exception has percolated outside the program, where it is finally caught and handled by the OS. It's as if your original program were written something like this:
__try
{
int main(void)
{
...
}
}
__except(exception_dialog(), EXCEPTION_EXECUTE_HANDLER)
{
}
Once you click Close on the dialog box, all termination handlers execute and the stack unwinds as before, until control returns to the catching handler. You see evidence of this in the lines:
4: handling abnormal termination
2: handling abnormal termination
which appear after you close the dialog box. Note that you don't see:
0:continuation
This is because the code writing that line is outside a termination handler, and only termination handlers are executed during the unwind.
Unfortunately for our test program, the catching handler is outside main, meaning post-exception execution continues beyond the program. As a result, the program terminates.
Next, change:
__except(except_filter(3, EXCEPTION_CONTINUE_SEARCH))
to:
__except(except_filter(3, EXCEPTION_CONTINUE_EXECUTION))
Rebuild, then rerun. You should see this output:
0:before first try
1: try
2: try
3: try
4: try
4: raising exception
3: filter => EXCEPTION_CONTINUE_EXECUTION
4: after exception
4: handling normal termination
3: continuation
2: continuation
2: handling normal termination
1: continuation
0:continuation
Because the level 3 filter caught the exception, the level 1 filter was never considered. That catching filter yields EXCEPTION_CONTINUE_EXECUTION. As a result, the exception is treated as resuming: The exception handler is not entered, and normal execution resumes at the point of the exception.
Notice the termination handlers. As their traces show, all are handling normal (non-exception) termination. This is further evidence that termination handlers, in the absence of exceptions, are normal code that is executed like any other.
Constructs like:
__try
{
/* ... */
return;
}
or:
__try
{
/* ... */
goto label;
}
__finally
{
/* ... */
}
/* ... */
label:
are considered abnormal termination of a try block. Any subsequent calls to AbnormalTermination will return non-zero, just as if an exception were active.
To see the effects of this, change the two lines:
trace(4, "raising exception");
RaiseException(exception_code, 0, 0, 0);
to:
trace(4, "exiting try block");
goto end_4;
Instead of being terminated by an exception, the level 4 try block is now terminated by a goto. The following run time results:
0:before first try
1: try
2: try
3: try
4: try
4: exiting try block
4: handling abnormal termination
3: continuation
2: continuation
2: handling normal termination
1: continuation
0:continuation
The level 4 termination handler believes it is handling an abnormal termination, even though no exception was raised. (Had a real exception occurred, however, we'd see traces from at least one exception filter.)
Moral: You cannot rely on AbnormalTermination to tell you conclusively that an exception is active.
If you want to terminate a try block normally, yet have AbnormalTermination return FALSE, use the Microsoft-specific keyword __leave. To demonstrate this, change:
goto end_4;
to:
__leave;
Rebuild and rerun the program as before. Results:
0:before first try
1: try
2: try
3: try
4: try
4: exiting try block
4: handling normal termination
3: continuation
2: continuation
2: handling normal termination
1: continuation
0:continuation
This output is identical to that in version #4, save one difference: The level 4 termination handler now perceives that it is handling normal termination.
All of the preceding program versions manage a user-generated exception. SEH can also manage exceptions thrown by Windows itself.
Change the lines:
trace(4, "exiting try block");
__leave;
to:
trace(4, "implicitly raising exception");
*((char *) 0) = 'x';
This causes a memory-access exception within Windows (dereferencing a null pointer). Also change:
__except(except_filter(3, EXCEPTION_CONTINUE_EXECUTION))
to:
__except(except_filter(3, EXCEPTION_EXECUTE_HANDLER))
so that the program catches and handles the exception.
The execution result is:
0:before first try
1: try
2: try
3: try
4: try
4: implicitly raising exception
3: filter => EXCEPTION_EXECUTE_HANDLER
4: handling abnormal termination
3: handling exception
2: continuation
2: handling normal termination
1: continuation
0:continuation
As expected, Windows raised an exception within level 4, which our handler caught in level 3.
If you are curious to know exactly which exception code was caught, you can let the exception travel outside main, as we did in version #2. To this end, change:
__except(except_filter(3, EXCEPTION_EXECUTE_HANDLER))
to:
__except(except_filter(3, EXCEPTION_CONTINUE_SEARCH))
The resulting dialog box, after you click Details>>, will be quite familiar even to casual Windows users.
'
Figure 3. The Memory Exception dialog box
Unlike the dialog box in version #2, which showed the specific exception code, this dialog box says only "invalid page fault"—presumably a more user-friendly diagnostic.
Of all the C-compatible exception handling mechanisms, SEH is far and away the most complete and flexible—at least within the Windows world. Ironically, it's also the least flexible outside of that world, because it weds you both to a particular target architecture, and to translators source-compatible with Visual C++.
If you program exclusively in C, or accept that your code won't port outside Windows targets, consider SEH. But if you program in C++ and care about portability, I strongly recommend you favor Standard C++ exceptions over SEH wherever you can. You can have both SEH and Standard C++ exceptions active in the same program, with one caveat: If you define an object within a function that contains an SEH try block, and that object has a non-trivial destructor, the compiler will issue an error. To mix such objects with __try in the same function, you must disable Standard C++ exceptions.
(Visual C++ turns Standard C++ exceptions off by default. You can turn them on either with the command-line option /GX or within the Microsoft Visual Studio® Project Settings dialog box.)
In a future column I'll revisit SEH in the context of Standard C++ exceptions. In particular, I'll want to integrate SEH into the C++ mainstream, by mapping structured exceptions and their Windows library support into C++ exceptions and Standard C++ Library support.
Caveat: this section requires that I foreshadow a bit, by making some references to Standard C++ exception handling—even though I won't formally introduce such handling until my next column. This forward reference is both unavoidable and unsurprising, since Microsoft bases their MFC exception syntax and semantics on those used by Standard C++.
All of the exception methods I've shown so far work in both C and C++. Beyond those, Microsoft has a solution for users of C++ programs: MFC exception-handling classes and macros. Microsoft now considers these MFC mechanisms obsolete, and instead encourages you to use Standard C++ exception handling wherever possible. However, Visual C++ still supports the MFC classes and macros for purposes of backward compatibility, so I will give the MFC mechanism a comparatively brief overview.
Microsoft uses Standard C++ exceptions to implement MFC versions 3.0 and later. As a result, you must activate Standard C++ exceptions to use MFC, even if you don't plan to use those exceptions explicitly. And as I just mentioned, you sometimes also want Standard C++ exceptions disabled when using SEH. This implies that you sometimes can't have both MFC macros and SEH active in the same program. Microsoft goes further, documenting that the two exception mechanisms are mutually exclusive and cannot be mixed in the same program.
Where SEH extended the compiler's keyword set, MFC defines a family of macros:
These macros are quite like the C++ exception keywords try, catch, and throw.
In addition, MFC provides a hierarchy of exception classes. All have names of the form CXXXException, and all are derived from the abstract base CException. This model is analogous to the Standard C++ Library's hierarchy derived from std::exception and declared in <stdexcept>. However, while Standard C++ can manage exception objects of most types, the MFC macros can manage only CException-derived objects.
For each MFC exception class CXXXException, there is a corresponding global helper function AfxThrowXXXException that constructs, initializes, and throws an object of that class type. You should use these helper functions for the predefined exception types, reserving the THROW macro for your own user-defined objects (which also must be derived from CException).
The basic design principles:
A small MFC sample that pulls most of the pieces together:
#include <stdio.h>
#include "afxwin.h"
void f()
{
TRY
{
printf("raising memory exception\n");
AfxThrowMemoryException();
printf("this line should never appear\n");
}
CATCH(CException, e)
{
printf("caught generic exception; rethrowing\n");
THROW_LAST();
printf("this line should never appear\n");
}
END_CATCH
printf("this line should never appear\n");
}
int main()
{
TRY
{
f();
printf("this line should never appear\n");
}
CATCH(CFileException, e)
{
printf("caught file exception\n");
}
AND_CATCH(CMemoryException, e)
{
printf("caught memory exception\n");
}
/* ... handlers for other CException-derived types ... */
AND_CATCH(CException, e)
{
printf("caught generic exception\n");
}
END_CATCH
return 0;
}
/*
When run yields
raising memory exception
caught generic exception; rethrowing
caught memory exception
*/
Remember, handlers catch object pointers, not actual objects. Thus, the handler:
CATCH(CException, e)
{
// ...
}
defines a local pointer CException *e, which references the thrown exception object. Thanks to C++ polymorphism, this pointer can reference any object derived from CException.
If you have multiple handlers for the same try block, the handlers are matched in top-to-bottom order. As a result, you should define handlers for more-derived objects before those for CException; otherwise the more specific handlers will never see the exception (again thanks to polymorphism).
Because you typically want to catch CException, MFC defines several CException-specific replacements for the standard macros:
The pointed-to exception objects are implicitly deallocated by MFC. This differs from Standard C++ exception handlers, which don't assume anyone takes ownership for caught pointers. As a result, you should not use both MFC and Standard C++ mechanisms to handle the same exception object; otherwise you risk memory leaks, references to deallocated objects, and multiple deallocations of the same objects.
MSDN Online has several other articles exploring both Structured Exception Handling and the MFC exception macros.
Next time I'll introduce Standard C++ exceptions, summarizing their features and rationale. I'll also compare and contrast them with the other methods we've seen so far.
Robert Schmidt is a technical writer for MSDN. His other major writing distraction is the C/C++ Users Journal (http://www.cuj.com/), for which he is a contributing editor and columnist. In previous career incarnations he's been a radio DJ, wild-animal curator, astronomer, pool-hall operator, private investigator, newspaper carrier, and college tutor.