EXCEPTION_CONTINUE_EXECUTION

[Previous] [Next]

Let's take a closer look at the exception filter to see how it evaluates to one of the three exception identifiers defined in Excpt.h. In the section called "Funcmeister2", the EXCEPTION_EXECUTE_HANDLER identifier is hard-coded directly into the filter for simplicity's sake, but you can make the filter call a function that will determine which of the three identifiers should be returned. Here's another code example:

 char g_szBuffer[100]; void FunclinRoosevelt1() { int x = 0; char *pchBuffer = NULL; _ _try { *pchBuffer = 'J'; x = 5 / x; } _ _except (OilFilter1(&pchBuffer)) { MessageBox(NULL, "An exception occurred", NULL, MB_OK); } MessageBox(NULL, "Function completed", NULL, MB_OK); } LONG OilFilter1(char **ppchBuffer) { if (*ppchBuffer == NULL) { *ppchBuffer = g_szBuffer; return(EXCEPTION_CONTINUE_EXECUTION); } return(EXCEPTION_EXECUTE_HANDLER); } 

We first run into a problem when we try to put a 'J' into the buffer pointed to by pchBuffer. Unfortunately, we didn't initialize pchBuffer to point to our global buffer g_szBuffer; pchBuffer points to NULL instead. The CPU will generate an exception and evaluate the exception filter in the except block associated with the try block in which the exception occurred. In the except block, the OilFilter1 function is passed the address of the pchBuffer variable.

When OilFilter1 gets control, it checks to see whether *ppchBuffer is NULL and, if it is, sets it to point to the global buffer g_szBuffer. The filter then returns EXCEPTION_CONTINUE_EXECUTION. When the system sees that the filter evaluated to EXCEPTION_CONTINUE_EXECUTION, it jumps back to the instruction that generated the exception and tries to execute it again. This time, the instruction will succeed, and 'J' will be put into the first byte of g_szBuffer.

As the code continues to execute, we run up against the divide by 0 problem in the try block. Again the system evaluates the exception filter. This time, OilFilter1 sees that *ppchBuffer is not NULL and returns EXCEPTION_EXECUTE_HANDLER, which tells the system to execute the except block code. This causes a message box to appear with text indicating that an exception occurred.

As you can see, you can do an awful lot of work inside an exception filter. Of course, the filter must return one of the three exception identifiers, but it can also perform any other tasks you want it to.

Use EXCEPTION_CONTINUE_EXECUTION with Caution

As it turns out, trying to correct the situation shown in the FunclinRoosevelt1 function and having the system continue execution might or might not work—it depends on the target CPU for your application, on how your compiler generates instructions for C/C++ statements, and on your compiler options.

A compiler might generate two machine instructions for the following C/C++ statement:

 *pchBuffer = 'J'; 

The machine instructions might look like this:

 MOV EAX, [pchBuffer] // Move the address into a register MOV [EAX], 'J' // Move 'J' into the address 

This second instruction generates the exception. The exception filter would catch the exception, correct the value in pchBuffer, and tell the system to re-execute the second CPU instruction. The problem is that the contents of the register wouldn't be changed to reflect the new value loaded into pchBuffer, and re-executing the CPU instruction would therefore generate another exception. We'd have an infinite loop!

Continuing execution might be fine if the compiler optimizes the code but might fail if the compiler doesn't optimize the code. This can be an incredibly difficult bug to fix, and you will have to examine the assembly language generated for your source code to determine what has gone wrong in your application. The moral of this story is to be extremely careful when returning EXCEPTION_CONTINUE_EXECUTION from an exception filter.

In one situation, EXCEPTION_CONTINUE_EXECUTION is guaranteed to work every time, all the time: when you are committing storage sparsely to a reserved region. In Chapter 15, we discussed how to reserve a large address space and then commit storage sparsely to this address space. The VMAlloc sample application demonstrated this. A better way to have written the VMAlloc application would have been to use SEH to commit the storage as necessary instead of calling VirtualAlloc all the time.

In Chapter 16, we talked about thread stacks. In particular, I showed you how the system reserved a 1-MB region of address space for the thread's stack and how the system automatically commits new storage to the stack as the thread needs it. To make this work, the system has internally set up an SEH frame. When your thread attempts to touch stack storage that doesn't exist, an exception is raised. The system's exception filter determines that the exception was due to an attempt to touch a stack's reserved address space. The exception filter then calls VirtualAlloc internally to commit more storage to your thread's stack and the filter returns EXCEPTION_CONTINUE_EXECUTION. At this point, the CPU instruction that attempted to touch the stack storage will now succeed and the thread continues running.

You can write some incredibly fast-performing and efficient applications when you combine virtual memory techniques with structured exception handling. The Spreadsheet sample application shown in the next chapter demonstrates how to efficiently implement the memory management portions of a spreadsheet application using SEH. This code is also designed to perform extremely fast.



Programming Applications for Microsoft Windows
Programming Applications for Microsoft Windows (Microsoft Programming Series)
ISBN: 1572319968
EAN: 2147483647
Year: 1999
Pages: 193

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net