Catching an Exception in a DLL

Background

On occaision, we have been asked to demonstrate a way for people using DLL's with VEE to catch exceptions in the functions they write. Exceptions are a mechanism that allow a process to recover from errors that would normally terminate the process, allowing a process to continue execution. For instance, if a DLL performs a division operation on data you pass in from a VEE program, there is the potential that the function could produce a divide-by-zero error.

You could try to prevent this error by explicitly checking each value for equality to zero before proceeding with the division. However, there are many conditions, in addition to divide-by-zero errors, that could throw an exception. Rather than coding prevention mechanisms for each anticipated condition, you could use an exception handler to better structure your DLL implementation, while allowing the process to continue to execute, even in the face of an error.

The remainder of this paper describes a technique, using the Windows NT (tm) Structured Exception Handling (SEH) mechanism, to allow you to create a DLL that captures exceptions generated in your DLL and prevent those exceptions from propagating back to the calling VEE process. We chose SEH over C++ style exceptions because a plain-old C function can use SEH and because some C++ compilers yield unpredictable results when exception handlers are used in conjunction with functions declared as extern "C"1.

Implementation

Implementing a section of code that is guarded against exceptions invloves:

The following code snippet illustrates the use of a try block:

	__try{
		x=5/divisor;
	}
When any of the statements inside the try block cause an error, the operating system will look for an exception filter function willing to deal with the error. Your function can elect to: In our specific example, we will catch and recover from divide-by-zero errors that the function in our DLL generates. We accomplish this by creating an exception filter function that uses a helper function to decide what of the above three actions to take.

Filter Block

Our exception filter looks like this:
	__except(evaluateException(GetExceptionCode())){
		/*
			We need to clear the condition which caused the exception.
			In the specific case of a divide-by-zero error, we can call
			'_clearfp', which will return the floating-point processor
			status word and clear the exception condition.
		*/
		
		fpStatus=_clearfp();

		/*
			We pass back a big number to indicate that something
			went wrong.
		*/
		
		x=9.9e37;
	}
Notice the call to the function GetExceptionCode. This function returns an indication of the error condition that caused us to generate an exception. It returns valid results only when called from within the parentheses immediately follwing the __except key word.

We said earlier that there are three possible actions a process can take in response to catching an exception. The function evaluateException, which is called from our exception filter, returns a value that indicates what action is to be taken in response to any given type of exception. If we have a divide-by-zero error, the return indicates that we should recover from the error by executing the code in the body of the __except block. Any other error condition results in a return value instructing the operating system to continue searching for an exception handler willing to deal with the error. If the operating system finds no exception handler willing to deal with the problem, it terminates the process (with extreme prejudice ;-) ).

If we are to prevent an exception from propagating back to the application that loaded our DLL, we must clear the condition which caused the error in the first place. In the case of a divide-by-zero error, we can clear the floating-point processor by making a call to the function _clearfp. This returns the floating-point processor's status and clears the condition which caused the exception. The following picture illustrates the result when we don't clear the error condition:

Results of Not Clearing the Exceptions's Cause

After executing the statements inside the exception filter, the process continues executing starting with the statements following the __except block. In this case, all we do is return a value indicating that something has gone wrong.

What to do, What to do?

Our exception filter calls the function evaluateException to decide what to do in response to detecting an error. This function is mostly a convenience to keep our code readable. The function listing follows:
int evaluateException(DWORD exceptionCode){
	int returnVal=EXCEPTION_CONTINUE_SEARCH;

	switch(exceptionCode){
		case EXCEPTION_FLT_DIVIDE_BY_ZERO:

			returnVal=EXCEPTION_EXECUTE_HANDLER;
			break;

		default:
			break;
	}
	
	return returnVal;
}
Basically, what we're saying here is that if the error was caused by a divide-by-zero error, we should execute the code in our __except block. Otherwise, tell the operating system to continue looking for an exception handler.

Source Files, DLL's, and Example Programs

You may feel free to do with the source files, DLL's, and example programs as you will, as long as you adhere to two simple rules: Implementation File
Header File
Linker Definition File
Make File
Pre-built DLL
Example VEE Program
Import Library Definition File

1For more information about the behavior of C++ exceptions in functions declared extern "C", refer to John Robbins' Bugslayer column in the October, 1999 edition of Microsoft Systems Journal.