QuickStart: Try-Catch exception handling

The Try-Catch form of error handling

vbWatchdog now supports a basic form of Try-Catch exception handling that can be used to simplify using a global error trap alongside local error handlers.

This is an example of a basic procedure when using the new Try-Catch concept:

Public Sub SimulateAnError()

    Debug.Print 1 / 0	' Simulate division by zero error (error 11)
    Debug.Print "A" / 1 ' Simulate type mismatch error (error 13)
    Err.Raise &H123, , "This should be caught by the catch &H123 block..."
    Err.Raise &H456, , "This should be caught in the catch-all block..."
    
    ' Program flow now transfers to Finally...
        
ErrEx.Catch 11, 13 ' you can set up a constant/enumeration if you prefer
    MsgBox "Error: either division by zero, or type mismatch!"
    Resume Next

ErrEx.Catch &H123 ' you can set up a constant/enumeration if you prefer
    MsgBox "Error: caught error 291 (&H123)!"
    Resume Next
            
ErrEx.CatchAll     
    MsgBox "Error (catch-all):" & vbCrLf & vbCrLf & Err.Description
    Resume Next
    
ErrEx.Finally
    MsgBox "Finally"
    Debug.Print 1 / 0    ' Errors here automatically ignored due to implicit OnErrorResumeNext
    
End Sub
Here you can see several "blocks" of code:
  • The main 'Try' block at the top (although you don't explicitly have to name it as such).
  • An ErrEx.Catch block for handlingboth error code 11 (division by zero) and 13 (type mismatch)
  • An ErrEx.Catch block for handling error code &H123
  • An ErrEx.CatchAll block for handling all other error codes
  • An ErrEx.Finally block usually used for cleaning up objects etc (setting to Nothing)

The rules

  • Any standard local error handling in the procedure (e.g. On Error Resume Next or On Error Goto XYZ) takes complete precedence over this feature.
  • You can have any number of ErrEx.Catch blocks to handle each specific error.
  • Each ErrEx.Catch statement can handle upto 4 error numbers (separated by commas)
  • You can still pass an error back to your global error trap by calling ErrEx.CallGlobalErrorHandler from within your ErrEx.Catch/CatchAll block.
  • The CatchAll block will only be executed if no other explicit Catch has been defined when an error is raised
  • All blocks are completely optional (e.g. you can have just a Catch or CatchAll on its own if you wish).
  • You are allowed only one CatchAll statement
  • You are allowed only one Finally statement
  • After a Catch block has executed, program flow will continue to the ErrEx.Finally block unless you explicitly Exit Sub/Function, Resume etc.
  • After the main block of code has executed (the implicit 'Try' block), program flow will continue to the ErrEx.Finally block unless you explicitly Exit Sub/Function etc.
  • You can call ErrEx.DoFinally to jump to the Finally block from elsewhere in your code (instead of using Exit Sub/Function/Property).
  • Error numbers specified on ErrEx.Catch lines must be literal constant values (not variables, but constants and enumerations are allowed).
  • The ErrEx.Finally block must be the final block in the code, appearing AFTER all ErrEx.Catch and ErrEx.CatchAll blocks

The new ErrEx.State values

When using this concept of error handling, you may wish to adjust your global error trap to take account of some new values of ErrEx.State:

OnErrorCatch This state indicates that the error that has been raised is due to be passed on to an ErrEx.Catch block that is defined in the procedure of error.

When your global error trap exits, vbWatchdog checks that the ErrEx.State value hasn't been altered. If it hasn't, then vbWatchdog will move program execution to the ErrEx.Catch block so that the error can be handled locally.

ErrEx.Number indicates which ErrEx.Catch block will ultimately catch the error.
OnErrorCatchAll This state indicates that the error that has been raised is due to be passed on to an ErrEx.CatchAll block that is defined in the procedure of error.

When your global error trap exits, vbWatchdog checks that the ErrEx.State value hasn't been altered. If it hasn't, then vbWatchdog will move program execution to the ErrEx.CatchAll block so that the error can be handled locally.
OnErrorPropagateCatch This state indicates that the error that has been raised is due to be passed on to an ErrEx.Catch block that is defined in a procedure deeper in the callstack (i.e. the top procedure doesn't have an ErrEx.Catch or CatchAll block to handle this error, but a procedure earlier in the callstack does).

When your global error trap exits, vbWatchdog checks that the ErrEx.State value hasn't been altered. If it hasn't, then vbWatchdog will unwind the callstack and move program execution to the ErrEx.Catch block so that the error can be handled locally.

ErrEx.Number indicates which ErrEx.Catch block will ultimately catch the error.
OnErrorPropagateCatchAll This state indicates that the error that has been raised is due to be passed on to an ErrEx.CatchAll block that is defined in a procedure deeper in the callstack (i.e. the top procedure doesn't have an ErrEx.Catch or CatchAll block to handle this error, but a procedure earlier in the callstack does).

When your global error trap exits, vbWatchdog checks that the ErrEx.State value hasn't been altered. If it hasn't, then vbWatchdog unwind the callstack and move program execution to the ErrEx.CatchAll block so that the error can be handled locally.
OnErrorInsideCatch This state indicates that the code in a local ErrEx.Catch block has itself caused an exception.

In standard VBA and VB6 error handling, errors that occur inside of a local error handler are treated as if no local error handling has been set (usually OnErrorGoto0, but can also be OnErrorPropagate).

vbWatchdog emulates this standard error handling behaviour as long as you don't alter the ErrEx.State value during the execution of your global error handler trap.
OnErrorInsideCatchAll Same as OnErrorInsideCatch state, but for the case when code within a ErrEx.CatchAll block causes an error.
OnErrorInsideFinally This state occurs if an exception is raised from within an ErrEx.Finally code block.

If you don't alter this value during the execution of your global error trap, then OnErrorResumeNext is implied so that errors that occur in the ErrEx.Finally block are effectively ignored.

Benefits of Try-Catch

The main benefit of our try-catch approach is that when your global error handler catches an error you know immediately whether an error is specifically being handled locally (e.g. div-by-zero in the example).  We know this since we can check if ErrEx.State in the global error trap equals OnErrorCatch or OnErrorPropagateCatch.

With a normal local error handler (such as on error goto xyz, which is similar to a CatchAll in the example), from within the global error handler you don’t know whether the local handler is actually designed to handle the specific error in question or not.  If it’s not, then your local handler ends up calling ErrEx.CallGlobalErrorHandler again so that the global error handler can log the error and show the error dialog.

The try-catch approach allows you to simplify and clean up that local error handling code.  With this approach, more often than not you won’t need a catch-all, but instead use a specific catch for a particular error number, and then the global error handler will treat all other errors as unhandled (keeping the error handling as tight as possible).

The ErrEx.Rethrow method [v3+]

The new ErrEx.Rethrow method can be called inside an ErrEx.Catch or ErrEx.CatchAll block to pass the error down the callstack. Ordinarily, errors that occur inside a catch block won't propagate as they have the special ErrEx.State values of OnErrorInsideCatch, or equivalent.

The Rethrow method has optional arguments to override the error number, source and description texts. If they are omitted, then ErrEx.Rethrow will use the error number and description of the last error. Please note that the 'last error' may or may not be the error being handled by your ErrEx.Catch block if you don't call ErrEx.Rethrow immediately after the catch block!

A practical example

To see a practical example, check out the Tips converting existing code (Example 4).