Software Exceptions

[Previous] [Next]

So far, we have been discussing hardware exceptions in which the CPU catches an event and raises an exception. It is also possible for your code to forcibly raise an exception. This is another way for a function to indicate failure to its caller. Traditionally, functions that can fail return some special value to indicate failure. The caller of the function is supposed to check for this special value and take an alternative course of action. Frequently, the caller has to clean up what it's doing and return its own failure code back to its caller. This propagating of error codes causes your source code to become much more difficult to write and to maintain.

An alternative approach is to have functions raise exceptions when they fail. With this approach, the code is much easier to write and to maintain. Plus, the code typically performs better without all of the error testing code being executed. In fact, the error-testing code only executes if there is a failure, and this is the exceptional case.

Unfortunately, most developers do not get into the habit of using exceptions for error handling. There are two basic reasons for this. The first reason is that most developers are unfamiliar with SEH. Even if one developer is acquainted with it, other developers might not be. If one developer writes a function that raises an exception but other developers don't write SEH frames to trap the exception, the process will be terminated by the operating system.

The second reason why developers avoid SEH is that it is not portable to other operating systems. Many companies target multiple operating systems and would like to have a single source code base for their products, which is certainly understandable. SEH is a Windows-specific technology.

However, if you decide to return errors via exceptions, I applaud your decision and this section is for you. First, let's look at the Windows Heap functions such as HeapCreate, HeapAlloc, and so on. You'll recall from Chapter 18 that these functions offer developers a choice. Normally when any of the heap functions fail, they return NULL to indicate failure. You can, however, pass the HEAP_GENERATE_EXCEPTIONS flag to any of these heap functions. If you use this flag and the function fails, the function does not return NULL; instead, the function raises a STATUS_NO_MEMORY software exception that other parts of your code can catch with an SEH frame.

If you want to take advantage of this exception, you can code your try block as though the memory allocation will always succeed; if the allocation fails, you can either handle the exception by using an except block or have your function clean up by matching the try block with a finally block. How convenient!

Your application traps software exceptions exactly the same way that it traps hardware exceptions. In other words, everything I said in the last chapter applies equally well to software exceptions.

What we want to concentrate on in this section is how to have your own functions forcibly raise software exceptions as a method for indicating failure. In fact, you can implement your functions similarly to Microsoft's implementation of the heap functions: have your callers pass a flag that tells your function how it should indicate failures.

Raising a software exception couldn't be easier. You simply call the RaiseException function:

 VOID RaiseException( DWORD dwExceptionCode, DWORD dwExceptionFlags, DWORD nNumberOfArguments, CONST ULONG_PTR *pArguments); 

The first parameter, dwExceptionCode, must be a value that identifies the raised exception. The HeapAlloc function passes STATUS_NO_MEMORY for this parameter. If you define your own exception identifiers, you should follow the same format as the standard Windows error codes as defined in the WinError.h file. Recall that each DWORD is divided as shown in Table 24-1.

If you create your own exception code, fill out all four fields of the DWORD:

  • Bits 31 and 30 will contain the severity.
  • Bit 29 will be 1 (0 is reserved for Microsoft-created exceptions, such as HeapAlloc's STATUS_NO_MEMORY).
  • Bit 28 is 0.
  • Bits 27 through 16 will be one of Microsoft's predefined facility codes.
  • Bits 15 through 0 will be an arbitrary value that you choose to identify the section of your application that raised the exception.

RaiseException's second parameter, dwExceptionFlags, must be either 0 or EXCEPTION_NONCONTINUABLE. Basically, this flag indicates whether it is legal for an exception filter to return EXCEPTION_CONTINUE_EXECUTION in response to this raised exception. If you do not pass the EXCEPTION_NONCONTINUABLE flag to RaiseException, the filter can return EXCEPTION_CONTINUE_EXECUTION. Normally, this would cause the thread to re-execute the same CPU instruction that raised the software exception. However, Microsoft has done some trickery so that execution continues after the call to the RaiseException function.

If you do pass the EXCEPTION_NONCONTINUABLE flag to RaiseException, you're telling the system that the type of exception you are raising can't be continued. This flag is used internally in the operating system to signal fatal (nonrecoverable) errors. In addition, when HeapAlloc raises the STATUS_NO_MEMORY software exception, it uses the EXCEPTION_NONCONTINUABLE flag to tell the system that this exception cannot be continued. This makes sense: there is no way to force the memory to be allocated and continue running.

If a filter ignores the EXCEPTION_NONCONTINUABLE flag and returns EXCEPTION_CONTINUE_EXECUTION anyway, the system raises a new exception: EXCEPTION_NONCONTINUABLE_EXCEPTION.

It is possible for an exception to be raised while the application is trying to process another exception. This makes sense, of course. While we're at it, let's note that it's also possible for an invalid memory access to occur inside a finally block, an exception filter, or an exception handler. When this happens, the system stacks exceptions. Remember the GetExceptionInformation function? This function returns the address of an EXCEPTION_POINTERS structure. The ExceptionRecord member of the EXCEPTION_POINTERS structure points to an EXCEPTION_RECORD structure that contains another ExceptionRecord member. This member is a pointer to another EXCEPTION_RECORD, which contains information about the previously raised exception.

Usually the system is processing only one exception at a time, and the ExceptionRecord member is NULL. However, if during the processing of one exception another exception is raised, the first EXCEPTION_RECORD structure contains information about the most recently raised exception and the ExceptionRecord member of this first EXCEPTION_RECORD structure points to the EXCEPTION_RECORD structure for the previously raised exception. If additional exceptions have not been processed completely, you can continue to walk this linked-list of EXCEPTION_RECORD structures to determine how to handle the exception.

RaiseException's third and fourth parameters, nNumberOfArguments and pArguments, are used to pass additional information about the raised exception. Usually, there is no need for additional arguments—you can simply pass NULL for the pArguments parameter, in which case RaiseException ignores the nNumberOfArguments parameter. If you do want to pass additional arguments, the nNumberOfArguments parameter must indicate the number of elements in the ULONG_PTR array pointed to by the pArguments parameter. This parameter cannot exceed EXCEPTION_MAXIMUM_PARAMETERS, which is defined in WinNT.h as 15.

During the processing of this exception, you can have an exception filter refer to the NumberParameters and ExceptionInformation members of the EXCEPTION_RECORD structure to examine the information in the nNumberOfArguments and pArguments parameters.

You might want to generate your own software exceptions in your application for any of several reasons. For example, you might want to send informational messages to the system's event log. Whenever a function in your application sensed some sort of problem, you could call RaiseException and have some exception handler further up the call tree look for certain exceptions and either add them to the event log or pop up a message box. You might also want to create software exceptions to signal internal fatal errors in your application.



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