8. Exception Handling

Page 130
  8. Exception Handling  
   
  As you proceed through this book, running the sample code and experimenting on your own, you will probably encounter a great many General Protection Faults (GPFs). That is only natural. In this chapter, we'll look at how Windows handles system and application errors that occur in Win32 API calls and how you can override its default error handling in your Visual Basic code.  
 
  Thwarting the General Protection Fault  
   
  Before we discuss how to work around GPFs, first consider the following code:  
 
  Dim lpDest As Long
Dim lng As Long

lng = 5

CopyMemory ByVal lpDest, ByVal VarPtr(lng), 1
 
   
  This code will produce a GPF because I forgot to set the value of the destination address lpDest. so it is simply initialized to 0. But writing to memory location 0 causes a GPF, with the accompanying message shown in Figure 8-1.  
   
  Of course, once the OK button is selected, Windows will terminate the application abruptly. There is no opportunity to recover from recover from such an error, as we can when writing pure VB code. Or is there?  
   
  Here is what is happening. When an exception (another word for error) occurs in a process, Windows looks for an exception handler to handle the exception. When Windows creates a process, it installs a default exception handler for the process,  
Page 131
   
  0131-01.gif  
   
  Figure 8-1.
Trying to write to memory location 0
 
   
  called UnhandledExceptionFilter. This function is called if the programmer has not installed a replacement exception handler (stay tuned).  
   
  UnhandledExceptionFilter first checks to see if the process being debugged is attached to a debugger. If not, as is the case for the VB programmer, the function will display a nasty error message such as the one in Figure 8-1. Subsequently, if the user selects the Cancel button, Windows will start a debugger for the thread. (By default, this is Visual C++ if it is installed on the user's system.) On the other hand, if the user selects the OK button, the UnhandledExceptionFilter function simply calls ExitProcess to terminate the process in which the thread is running.  
 
  Replacing the Default Exception Handler  
   
  Now to the point. There is a Win32 API function called SetUnhandledExceptionFilter. To quote the documentation:  
  The SetUnhandledExceptionFilter function lets an application supersede the top-level exception handler that Win32 places at the top of each thread and process.  
   
  The VB syntax for the SetUnhandledExceptionFilter function is:  
 
  Declare Function SetUnhandledExceptionFilter Lib "kernel32" ( _
   ByVal lpTopLevelExceptionFilter As Long _
) As Long
 
   
  where the parameter lpTopLevelExceptionFilter is the address of the replacement exception handler.  
   
  Thus, we need only to make the call:  
 
  SetUnhandledExceptionFilter AddressOf NewExceptionHandler  
   
  where NewExceptionHandler is a replacement exception handler in a VB standard code module.  
Page 132
   
  The SetUnhandledExceptionFilter function returns the address of the previous exception handler, but we will not need this address. To restore the original default handler, we just call the function with the lpTopLevelExceptionFilter parameter set to 0.  
 
  The Replacement Exception Handler  
   
  The replacement exception handler should have the signature:  
 
  Function NewExceptionHandler(ByRef lpExceptionPointers As _
                             EXCEPTION_POINTERS) As Long
 
   
  This function receives the address of an EXCEPTION_POINTERS structure in the parameter lpExceptionPointers. This structure allows the replacement exception handler to get information about the exception. In particular, the structure is defined as follows:  
 
  Type EXCEPTION_POINTERS
   pExceptionRecord As Long   'pointer to an EXCEPTION_RECORD struct
   pContextRecord As Long     ' pointer to a CONTEXT struct
End Type
 
   
  The second member of this structure is a pointer to a CONTEXT structure that contains information about the state of the machine at the time the exception handler is called (that is, at the time of the exception). This structure reports such things as the state of the CPU registers. However, we will not go into this further, since it is not relevant to our discussion.  
   
  On the other hand, the first member points to a structure defined as follows:  
 
  Type EXCEPTION_RECORD
   ExceptionCode As Long
   ExceptionFlags As Long
   pExceptionRecord As Long  ' Pointer to an EXCEPTION_RECORD struct
   ExceptionAddress As Long
   NumberParameters As Long
   ExceptionInformation(EXCEPTION_MAXIMUM_PARAMETERS) As Long
End Type
 
   
  Let us briefly describe the members of this structure.  
 
  ExceptionCode
Gives the code number for the exception. The possible values are shown in Table 8-1.
 
Table 8-1. Exception Codes
Exception Codes Value
EXCEPTION_ACCESS_VIOLATION &HC0000005&
EXCEPTION_ARRAY_BOUNDS_EXCEEDED &HC0000008C&
EXCEPTION_BREAKPOINT &H80000003&


   
  (table continued on next page.)  
Page 133
   
  (table continued from previous page.)  
Table 8-1. Exception Codes (continued)
Exception Codes Value
EXCEPTION_DATATYPE_MISALIGNMENT &H80000002&
EXCEPTION_FLT_DENORMAL_OPERAND &HC000008D&
EXCEPTION_FLT_DIVIDE_BY_ZERO &HC000008E&
EXCEPTION_FLT_INEXACT_RESULT &HC000008F&
EXCEPTION_FLT_INVALID_OPERATION &HC0000090&
EXCEPTION_FLT_OVERFLOW &HC0000091&
EXCEPTION_FLT_STACK_CHECK &HC00000092&
EXCEPTION_FLT_UNDERFLOW &H80000093&
EXCEPTION_GUARD_PAGE &HC0000001&
EXCEPTION_ILLEGAL_INSTRUCTION &HC000001D&
EXCEPTION_IN_PAGE_ERROR &HC0000006&
EXCEPTION_INT_DIVIDE_BY_ZERO &HC0000094&
EXCEPTION_INT_OVERFLOW &HC0000095&
EXCEPTION_INVALID_DISPOSITION &HC0000026&
EXCEPTION_INVALID_HANDLE &HC000008&
EXCEPTION_NONCONTINUABLE_EXCEPTION &HC00000025&
EXCEPTION_PRIV_INSTRUCTION &HC0000096&
EXCEPTION_SINGLE_STEP &H80000004&
EXCEPTION_STACK_OVERFLOW &HC000000FD&


 
  ExceptionFlags
A value of 0 indicates a continuable exception. A value of EXCEPTION_NONCONTINUABLE_EXCEPTION (&HC0000025&) indicates a noncontinuable exception. Any attempt to continue execution after a noncontinuable exception will generate an exception with exception code EXCEPTION_NONCONTINUABLE_EXCEPTION.
 
 
  pExceptionRecord
According to the documentation, exceptions may be nested (although the documentation does not say how this can happen). If the pExceptionRecord member is nonzero, then it points to an EXCEPTION_RECORD structure that contains information about a nested exception.
 
 
  ExceptionAddress
This contains the address of the instruction that caused the exception.
 
 
  NumberParameters
This value is the number of valid entries in the ExceptionInformation array (coming next).
 
Page 134
 
  ExceptionInformation
This is used in only one case. When the exception is an access violation (exception code EXCEPTION_ACCESS_VIOLATION), NumberParameters is set to 2, and ExceptionInformation(0) contains a 0 if there was an illegal read attempt and 1 if there was an illegal write attempt. In either case, ExceptionInformation(1) is the memory address at which the read/write attempt was made.
 
   
  The documentation states that the replacement exception handler NewExceptionHandler must return one of the following values, which determines the next course of events:  
 
  EXCEPTION_EXECUTE_HANDLER
Causes process termination.
 
 
  EXCEPTION_CONTINUE_EXECUTION
Continues execution at the point of the exception. For us, this will simply mean repeating the exception.
 
 
  EXCEPTION_CONTINUE_SEARCH
Calls Windows default exception handler.
 
   
  However, the documentation is addressed to VC++ programmers. We VB programmers have another trick up our sleeves.  
   
  To understand this trick, let us recall how VB handles errors within its own code. If an error occurs, VB will check the procedure causing the error for an error handler (On Error  ). If none is found, then VB will climb the call stack looking for an active error handler. If none is found, then VB issues its own unpleasant error message and terminates the program.  
   
  However, in looking through the call stack, VB will skip over any external procedures that is, any nonVB code. Figure 8-2 shows the call stack when a breakpoint is reached in a replacement exception handler NewExceptionHandler.  
   
  0134-01.gif  
   
  Figure 8-2.
The call stack
 
Page 135
   
  The first procedure is the Click event shown in Example 8-1, which caused the exception in the kernel32.dll library that exports the CopyMemory function. Thus, the reference to [<Non-Basic Code>] in Figure 8-2 refers to the external CopyMemory function.  
   
  Example 8-1. A Click Event That Causes an Exception  
   
  Private Sub cmdRaiseException_Click()

Dim lpDest As Long
Dim lng As Long

On Error GoTo ERR_RaiseException

lng = 5

CopyMemory ByVal lpDest, ByVal VarPtr(lng), 1

MsgBox  Recovered from GPF
Exit Sub
ERR_RaiseException:
   MsgBox Err.Description
   Resume Next
End Sub
 
   
  Now, in our replacement exception handler NewExceptionHandler, we do three things:  
   
  We collect information about the exception in a string variable, say sError.  
   
  We deliberately avoid enabling a VB error handler in NewExceptionHandler. In other words, we do not use On Error in this procedure.  
   
  We deliberately place code in NewExceptionHandler to raise a VB error, using the sError value as the description of the error, as in:  
 
  Err.Raise 1000, "NewExceptionHandler", sError  
   
  Now, simply put, if an exception occurs, the call to Err.Raise will cause VB to look for a VB error handler. Since none exists in the offending NewExceptionHandler procedure, VB will search the call stack, skipping over the external code that caused the exception. Hence, in the case shown in Figure 8-2, the error handler in the cmdRaiseException_Click event will fire, thus displaying the description of the exception in a VB message box, as shown in Figure 8-3.  
   
  We now have complete control over what happens next. For instance, in the error-handling code in cmdRaiseException_Click, we call Resume Next to continue execution, but of course there are other possibilities. The point is that the VB application will not terminate abruptly.  
Page 136
   
  0136-01.gif  
   
  Figure 8-3.
A VB message box describing the GPF
 
   
  Before turning to a complete example, we should mention that this approach to exception handling can be very helpful. However, for the sake of space and clarity of purpose, we will not employ it in the examples in this book.  
 
  A Complete Example  
   
  Example 8-2 shows the code for the NewExceptionHandler replacement exception handler. A complete application is on the accompanying CD. The GetException function below just returns the symbolic constant from Table 8-1 when given the constant's value.  
   
  Example 8-2. The NewExceptionHandler Replacement Exception Handler  
   
  Function NewExceptionHandler(ByRef lpExceptionPointers As EXCEPTION_POINTERS) As Long

' No need to return a value since Err.Raise will alter execution flow

Dim er As EXCEPTION_RECORD
Dim sError As String

' Make a copy of the exception record portion
' of the passed-in EXCEPTION_POINTERS structure
CopyMemory er, ByVal lpExceptionPointers.pExceptionRecord, Len(er)

' Set up error description string
Do
   sError = GetException(er.ExceptionCode)
   ' Special treatment for access violation -- get addresses
   If sError =  EXCEPTION_ACCESS_VIOLATION  Then
      sError = sError &   - Instr @ &H  & Hex(er.ExceptionAddress) _
         &   tried illegally to   _
         & IIf(er.ExceptionInformation(0) = 0, _
                read from address ,  write to address ) _
         &   &H  & Hex(er.ExceptionInformation(1))
   End If

   ' Check for nested error
   If er.pExceptionRecord = 0 Then Exit Do
 
Page 137
   
  Example 8-2. The NewExceptionHandler Replacement Exception Handler (continued)  
   
     ' Nested error exists
   ' Replace this er by the nested er
   CopyMemory er, ByVal er.pExceptionRecord, Len(er)

   ' New line for next error
   sError = sError & vbCrLf
Loop

' Raise an error to go up the call stack, passing the externally
' generated error!
Err.Raise 1000,  NewExceptionHandler , sError

End Function
 



WIN32 API Programming with Visual Basic
Win32 API Programming with Visual Basic
ISBN: 1565926315
EAN: 2147483647
Year: 1999
Pages: 31
Authors: Steven Roman

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