Upcoming Events
Games for Health Fourth Annual Conference
5/8 - 5/9 @ Baltimore, MD

Toronto Indie Game Jam #3
5/9 - 5/11 @ Toronto, Canada

ION Game Conference
5/13 - 5/15 @ Seattle, WA

Nordic Game 2008
5/14 - 5/15 @ Malmö, Sweden

More events...


Quick Stats
6214 people currently visiting GDNet.
2169 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!



Link to us

  search:   

The Visual C++ Exception Model



Contents
  Page 1
  Page 2
  Page 3
  Page 4

  Printable version
  Discuss this article

When programming in C++ with Microsoft Visual C++, your program has two exceptions models: the normal C++ exception model and the Structure Exception Handling (SEH) exception model.1) MSVC's C++ exception system is, in fact, implemented with SEH's functionality. This implementation leads to some interesting behaviors.

This article discusses some of the consequences of the MSVC exception system from the standpoint of a C++ programmer. It assumes familiarity with C++ exceptions, including exception aware programming techniques like RAII, and basic Windows programming.2) It does not assume any background with SEH. The behavior described covers MSVC .NET 2002, 2003, 2005 and 2008.

An Introduction to Structured Exception Handling

Structured Exception Handling is a mechanism built into the operating system that provides facilities to detect and deal with exceptional circumstances, much as C++ exceptions do for C++ programs. SEH exceptions come in two varieties: hardware exceptions and software exceptions. Hardware exceptions are exceptions raised by the CPU such as access violations and divide by zero. Software exceptions are exceptions raised explicitly by programs or, on occasion, the operating system itself. For the purposes of this article, there isn't much difference between the two.

When an exception is raised, two pieces of information are captured: one is the state of the processor at the time of the exception, which is stored in a CONTEXT structure3). The other is information about the exception, which is stored in an EXCEPTION_RECORD. The two parts of this structure we'll be concerned about are the ExceptionCode member and the ExceptionInformation array, which contain a numeric value for the exception type and additional information respectively. For example, when an access violation is detected, a hardware exception is raised with the code 0xC0000005. If ExceptionInformation[0] is zero the access violation was caused by a read and if ExceptionInformation[0] is non-zero the access violation was triggered by a write, and ExceptionInformation[1] is the address that was read from or written to. Not all exceptions generate additional information; in fact, the majority don't. To raise a software exception, code can call the RaiseException() function, which takes an exception code and other exception information, which will then be packaged into an EXCEPTION_RECORD.

Windows exception codes follow the following format: Bits 31 and 30 represent the severity code. It should be 00 for success, 01 for informational, 10 for warning and 11 for an error. Bit 29 is set for user exception codes. Bit 28 is reserved and should not be set. Bits 27 to 16 represent a facility code. For example, Windows Update is assigned the facility code of 36, while 0 represents no particular facility. The bottom 16 bits then represent the actual error code. This is why exceptions like access violations start with 0xC: these codes represent non-user error exceptions. In theory, most user exception codes should start with 0xE to represent user defined error exceptions4).

Actually dealing with SEH exceptions in MSVC takes two primary forms: frame based exception handling (__try/__except blocks) and termination handling (__try/__finally blocks).

Frame Based Exception Handling

Frame based exception handling is roughly analogous to C++'s try/catch blocks. Instead of try/catch, the blocks look like:

__try {
  // code to attempt to execute
} __except (filter_expression) {
  // handler code
}
The block after the __try is executed, and if an SEH exception is thrown the filter expression is evaluated. __try blocks have many of the restrictions, both de jure and de facto, that C++ try blocks have. For example, goto cannot be used to enter __try blocks and __try blocks inhibit inlining.

Unlike C++ catch block, where the catch contains types, the filter expression is more like the contents of the parenthesis after an if5): it can be almost anything that eventually evaluates to a value. In particular, the filter expression must evaluate to one of three values: EXCEPTION_EXECUTE_HANDLER, EXCEPTION_CONTINUE_SEARCH or EXCEPTION_CONTINUE_EXECUTION. If the expression evaluates to EXCEPTION_EXECUTE_HANDLER, the code in the handler block is executed, much like when the type matches the catch for a C++ exception. If the expression evaluates to EXCEPTION_CONTINUE_SEARCH it looks for another __except block that might handle the exception, like when a C++ catch block doesn't contain a matching type 6). EXCEPTION_CONTINUE_EXECUTION has no analogy in C++ exception handling: it causes program execution to resume from the point where the exception was raised 7).

Unlike C++ catch blocks, you can't chain __except blocks. That is to say this code is illegal:

  __try {
    // stuff
  } __except (filter expression 1) {
    // handler 1
  } __except (filter expression 2) {
    // handler 2

  }
You can, instead, nest the blocks.
  __try {
    __try {
      // stuff
    } __except (filter expression 1) {
      // handler 1
    }
  } __except (filter expression 2) {
    // handler 2

  }
Another, much bigger limitation of SEH blocks is that functions containing SEH blocks cannot contain C++ objects with unwind semantics. This translates into basically anything with a destructor is illegal to create as a auto or stack variable in a function with SEH blocks8). You also can't use C++ try/catch blocks. 9)

So what does a typical filter expression look like? If there was such a thing as a typical filter expression, it would probably look something like this:

  EXCEPTION_POINTERS * eps = 0;
  __try {
    // stuff

  } __except (eps = GetExceptionInformation(), EXCEPTION_EXECUTE_HANDLER) {
    // handler
  }
GetExceptionInformation() is a macro that obtains pointers to the CONTEXT and EXCEPTION_RECORD structures mentioned earlier. It can only be called inside a filter expression; it can't be called in the __except block. So this filter expression assigns the exception pointers and then uses the comma operator to have the filter expression take on the value of EXCEPTION_EXECUTE_HANDLER. This rapidly gets unwieldy if the expression needs to have any additional logic rather unconditionally returning a single value. Fortunately, it is legal to call functions in the filter expression.10)
DWORD filter(EXCEPTION_POINTERS * eps) {
  if (eps->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
    return EXCEPTION_EXECUTE_HANDLER;
  return EXCEPTION_CONTINUE_SEARCH;
}
 
  EXCEPTION_POINTERS * eps = 0;
  __try {
  } __except (eps = GetExceptionInformation(), filter(eps)) {
  }
Another macro used in filter expressions is GetExceptionCode(). Like GetExceptionInformation() it can only be called inside a filter expression. As you might guess from the name, it returns the exception code of the exception being filtered. With that you could write the above filter expression like so:
  EXCEPTION_POINTERS * eps = 0;
  __try {
  } __except (eps = GetExceptionInformation(), 
              ((GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION) 
                  ? EXCEPTION_EXECUTE_HANDLER 
                  : EXCEPTION_CONTINUE_SEARCH)) {
  }

Termination Handling

Termination handling is much simpler than frame based exception handling. Instead of __try/__except, we have __try/__finally. The __try block is executed, and barring the thread or process ending, the code in the __finally block is executed unconditionally when the __try block is exited, whether the function returns, an exception is thrown, if there's a goto out of the block or the last statement in the __try block finishes executing.

  __try {
    // try to execute this code
  } __finally {
    // execute this code afterwards (unless the thread or process ends)
  }
Like __try/__except blocks, __try/__finally blocks can't be used in functions with objects with stack unwind semantics. You also cannot tag a __finally block on a __try/__except, though you can nest a __try/__finally block within a __try/__except (or vice versa).
  __try {
    __try {
      // try to execute this code
    } __finally {
      // execute this code afterwards (unless the thread or process ends)
    }
  } __except (filter_expression) {
    // exception handler
  }
Termination handling brings one new concept to the table: abnormal termination. A __try block is said to end normally or abnormally. Normal termination means that program flow continued after the last statement in the __try block was executed and continued into the __finally block. Abnormal termination means that the control flow exited the block in any other way, including exceptions, but also include return, goto, continue or break statements; even if one of those control flow statements was the last statement in the __try block. Abnormal termination results in a performance penalty as the SEH code searches for all relevant __finally handlers. Abnormal termination can also be avoided with the __leave keyword, which immediately ends the __try block and starts the __finally block. Inside the __finally block, the AbnormalTermination() macro can be used to determine if the __try block was left normally or abnormally.


1) You can disable the C++ exception model by changing your project properties and avoiding exception code, but you're stuck with SEH no matter what you do. Even if you never put a single __try block in your code, the operating system wraps your program in a SEH handler that produces the standard dialogs for access violations and the like.
2) Such as recognizing what an access violation is, the basic typedefs and the difference between the ANSI and Unicode versions of systems calls.
3) This is highly processor dependent information such as the contents of registers and the state of the floating point unit.
4) In practice, user defined exception codes range across the board, since the RaiseException() documentation only mentions the effect of bit 28 and the rest of the Platform SDK documentation on SEH doesn't mention the bit information for exception codes. These guidelines are actually mentioned in winerror.h and the Visual C++ language reference for SEH and not many other places.
5) With some additional restrictions such as not being able to define a new variable in the filter expression.
6) If there is no enclosing __except block in the executable code, the exception handler installed by the operating system triggers, which generally displays an unhandled exception dialog and then ends the program.
7) Unless the exception was raised with the EXCEPTION_NONCONTINUABLE flag, in which case if the filter expression returns EXCEPTION_CONTINUE_EXECUTION this causes an EXCEPTION_NONCONTINUABLE_EXCEPTION exception.
8) However, pointers and references to such objects can be used.
9) It may help to think of functions with SEH blocks to be C only, though this isn't strictly true.
10) This is not necessarily a good thing in all instances. Since filter expressions are evaluated in the context of the exception, calling a function during a stack overflow could cause additional problems.




Page 2