Getting up to speed on exception handling can be tough partly because C++ can use two main types of exception handling: structured exception handling (SEH), which the operating system provides, and C++ exception handling, which the C++ language provides. Just figuring out which type of exception handling to use when can be a challenge, and it doesn't help that many people talk about both types as if they were interchangeable. I assure you that each type of exception handling has a distinctly different approach. I think what confuses some people is that you can combine both types. In the following sections, I'll touch on the differences and similarities between these two types of exception handling. I'll also describe how to combine them in a way that will let you avoid some of the problems that can crop up when you use them together.
The operating system provides SEH, and it deals directly with crashes such as access violations. SEH is language-independent but is usually implemented in C and C++ programs with the __try / __except and __try / __finally keyword pairs. The way you use the __try / __except pair is to set your code inside a __try block and then determine how to handle the exception in the __except block (also called an exception handler). In a __try / __finally pair, the __finally block (also called a termination handler) ensures that a section of code will always be executed upon leaving a function, even if the code in the __try block terminates prematurely.
Listing 9-1 shows a typical function with SEH. The __
except
block looks almost like a function call, but the parentheses specify the value of a special expression called an
exception filter
. The exception filter value in Listing 9-1 is
EXCEPTION_EXECUTE_HANDLER
, which indicates that the code in the __
except
block must be executed every time any exception occurs inside the
__try
block. The two other possible exception filter values are
EXCEPTION_CONTINUE_EXECUTION
, which allows an exception to be ignored, and
EXCEPTION_CONTINUE_SEARCH
, which
Listing 9-1 Example SEH handler
voidFoo(void)
{
__try
{
__try
{
//Executecodetoaccomplishsomething.
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
//Thisblockwillbeexecutedifthecodeinthe
//blockcausesanaccessviolationorsomeotherhardcrash.
//Thecodeinhereisalsocalledtheexceptionhandler.
}
}
__finally
{
//Thisblockwillbeexecutedregardlessofwhetherthefunction
//causesacrash.Mandatorycleanupcodegoeshere.
}
}
|
The process of finding and executing an exception handler is sometimes called
Your exception handler can determine the exception value by calling the special
GetExceptionCode
function, which can be called only in exception filters. If you were writing a math package, for example, you might have an exception handler that handles divide-by-zero attempts and returns NaN (not a number). The code in Listing 9-2 shows an example of such an exception handler. The exception filter calls
GetExceptionCode
, and if the exception is divide-by-zero, the exception handler executes. If any other exception occurs,
EXCEPTION_CONTINUE_SEARCH
Listing 9-2 Example SEH handler with exception filter processing
longIntegerDivide(longx,longy)
{
longlRet;
__try
{
lRet=x/y;
}
__except(EXCEPTION_INT_DIVIDE_BY_ZERO==
GetExceptionCode()
?EXCEPTION_EXECUTE_HANDLER
:EXCEPTION_CONTINUE_SEARCH)
{
lRet=NaN;
}
return(lRet);
}
|
If your exception filter requires more complexity, you can even call one of your own functions as the exception filter as long as it specifies how to handle the exception by returning one of the valid exception filter values. In addition to calling the special GetExceptionCode function, you can also call the GetExceptionInformation function in the exception filter expression. GetExceptionInformation returns a pointer to an EXCEPTION_POINTERS structure that completely describes the reason for a crash and the state of the CPU at that time. You might have guessed that the EXCEPTION_POINTERS structure will come in handy later in this chapter.
SEH isn't limited just to handling crashes. You can also create your own exceptions with the RaiseException API function. Most developers don't use RaiseException , but it does offer a way to quickly leave deeply nested conditional statements in code. The RaiseException exit technique is cleaner than using the old setjmp and longjmp run-time functions.
Before you dive in and start using SEH, you need to be aware of two of its limitations. The first is minor: your custom error codes are limited to a single unsigned integer. The second limitation is a little more serious: SEH doesn't mix well with C++ programming because C++ exceptions are implemented with SEH and the compiler complains when you try to combine them indiscriminately. The reason for the conflict is that when straight SEH unwinds out of a function, it doesn't call any of the C++ object destructors for objects created on the stack. Because C++ objects can do all sorts of initialization in their constructors, such as allocating memory for internal data structures, skipping the destructors can lead to memory leaks and other problems.
If you'd like to learn more about SEH, I recommend two references in addition to perusing the Microsoft Developer Network (MSDN). The best overview of SEH is in Jeffrey Richter's
Programming Applications for Microsoft
Windows
(Microsoft Press, 1999). If you're
Because C++ exception handling is part of the C++ language specification, it's probably more familiar to most programmers than SEH. The keywords for C++ exception handling are try and catch . The throw keyword allows you to initiate an exception unwind. Whereas SEH error codes are limited to just a single unsigned integer, a C++ catch statement can handle any variable type, including classes. If you derive your error handling classes from a common base class, you can handle just about any error you need to in your code. This class hierarchy approach to error handling is exactly what the Microsoft Foundation Class (MFC) library does with its CException base class. Listing 9-3 shows C++ exception handling in action with an MFC CFile class read.
Listing 9-3 C++ exception handler example
BOOLReadFileHeader(CFile*pFile,LPHEADERINFOpHeader)
{
ASSERT(FALSE==IsBadReadPtr(pFile,sizeof(CFile*)));
ASSERT(FALSE==IsBadReadPtr(pHeader,
sizeof(LPHEADERINFO)));
if((TRUE==IsBadReadPtr(pFile,sizeof(CFile*)))
(TRUE==IsBadReadPtr(pHeader,
sizeof(LPHEADERINFO))))
{
return(FALSE);
}
BOOLbRet;
try
{
pFile->Read(pHeader,sizeof(HEADERINFO));
bRet=TRUE;
}
catch(CFileException*e)
{
//Iftheheadercouldn'tbereadbecausethefilewas
//truncated,handleit;otherwise,continuetheunwind.
if(CFileException::endOfFile==e->m_cause)
{
e->Delete();
bRet=false;
}
else
{
//Thethrowkeywordjustbyitselfthrowsthesameexception
//aspassedtothiscatchblock.
throw;
}
}
return(bRet);
}
|
You need to keep in mind the following drawbacks when you're using C++ exception handling: First, it doesn't handle your program crashes automatically. (Later in the chapter, I'll show you how you can work around this limitation.) Second, C++ exception processing isn't free. The compiler might do a great deal of work setting up and removing the try and catch blocks even if you never throw any exceptions, so if you're working on performance-sensitive code you might not be able to afford that much overhead. Although these performance-sensitive cases are rare, they do occur. If you're new to C++ exception handling, MSDN is a great place to start learning about it.
As mentioned earlier, there is a way to combine both SEH and C++ exceptions; the result is that you have to use only C++ exception handling in your code. The C run-time library function _set_se_translator lets you set a translator function that will be called when a structured exception happens so that you can throw a C++ exception. This powerful function is a hidden gem. The following code snippet shows all that a translator function must do:
voidSEHToCPPException(UINTuiEx,
EXCEPTION_POINTERS*pExp)
{
//CSEHExceptionisaclassderivedfromtheMFCCException
//class.
throwCSEHException(uiEx,pExp);
}
|
The first parameter is the SEH code returned through a call to GetExceptionCode . The second parameter is the exception state from a call to GetExceptionInformation .
When using the
_set_se_translator
function in your code, you should catch exception classes thrown out of your translator function only if you're expecting a crash. For example, if you allow users to extend your application with DLLs, you can wrap the calls to the DLLs with
try...catch
blocks to handle potential crashes. In the course of normal processing, however, you should end the application when you get a hard SEH crash. In one of my own programs, I
The reason you should be careful about handling hard SEH exceptions as C++ exceptions is that the process is in an unstable state. You can show dialog boxes and write out crash information to a file as part of your handling. However, you need to be aware that the stack might be blown and so you don't have the room to call many functions. Because the exception code is passed to your translator function, you need to check it for EXCEPTION_STACK_OVERFLOW and degrade your error handling gracefully if there is insufficient stack space.
As you can see in the
When using C++ exception handling, you must understand the difference between asynchronous and synchronous exception handling. Unfortunately, the words asynchronous and synchronous don't adequately describe the difference between these two types of C++ exception handling. The real difference between asynchronous and synchronous C++ exception handling comes down to this distinction: how the compiler assumes that exceptions will be thrown dictates how the compiler generates the exception handling code for the program.
In asynchronous C++ exception handling, the compiler assumes that each instruction could generate an exception and that the code must be prepared to handle the exceptions
With synchronous exception handling, the default for Visual C++ 6, the compiler expects you to throw exceptions only with an explicit throw statement. Thus the compiler doesn't have to track the lifetime of an object and doesn't have to generate the code needed to handle the unwinding if the object's lifetime doesn't overlap a function call or a throw statement. You can think of asynchronous as "all functions track lifetimes of objects" and synchronous as "some functions track lifetimes of objects."
The impact of the switch to synchronous exception handling as the default is that in your release builds you can end up in situations in which your
If you want to compile with asynchronous exception handling, /EHa, but without the overhead of the object lifetime tracking on functions that don't throw exceptions, you can use __declspec(nothrow) to declare or define those functions. Although you have to do more work by manually declaring __declspec(nothrow) , you reap the benefits of _set_se_translator and tighter code.
The code in Listing 9-4 shows a program using
_set_se_translator
that doesn't work if compiled as a release build with the default synchronous exceptions. The code must be compiled with /EHa. In your programs, if you want to ensure that you can use
_set_se_translator
anywhere, including in functions outside classes, you must compile with /EHa and take the hit of the extra code overhead. If you have a C++ program,
Listing 9-4 An example in which synchronous exceptions don't work
//CompileasaWin32Releaseconfigurationusing/GXtoseethatthe
//translatorfunctionwon'tbecalled./GXmapsto/EHsc.Tomake
//thisprogramworkinareleasebuild,compilewith/EHa.
#include"stdafx.h"
classCSEHError
{
public:
CSEHError(void)
{
m_uiErrCode=0;
}
CSEHError(unsignedintu)
{
m_uiErrCode=u;
}
~CSEHError(void)
{
}
unsignedintm_uiErrCode;
};
voidTransFunc(unsignedintu,EXCEPTION_POINTERS*pEP)
{
printf("InTransFunc\n");
throwCSEHError(u);
}
voidGrungyFunc(char*p)
{
*p='p';
printf("Thisoutputshouldneverbeseen!\n");
}
voidDoBadThings(void)
{
try
{
GrungyFunc((char*)0x1);
}
catch(CSEHErrore)
{
printf("Gotanexception!->0x%08X\n",e.m_uiErrCode);
}
}
intmain(intargc,char*argv[])
{
_set_se_translator(TransFunc);
DoBadThings();
return0;
}
|