While the basic error-handling concepts are the same, errors within an ActiveX server class have to be handled slightly differently than errors in a client application. Some of the important differences include:
Don't display the error
Unlike a client application, in which you might display a message box to the user detailing the problem, you should remember that with a server-side application, there might be no one there to click OK! Instead, you should write an entry into an event log. (Section 6.4 details how to write an event log.) If you have a large application that already has many MsgBox calls, and you don't want to spend ages rewriting this code, simply go to the project properties dialog and select the Unattended Execution option. This forces all MsgBox calls to be written to an event log.
Use Err.Raise
Once you've logged the error in your server class, you need some way of informing the user that an error occurred. The simplest method of doing this is to raise an error using the Err.Raise method. This error will be picked up by the client's error handler, and the relevant message displayed. This simple client and server code demonstrates the Err.Raise method:
Client code:
Private Sub Command3_Click() On Error GoTo Command3_Err Dim oTest As TestErrors.DoStuff Set oTest = New TestErrors.DoStuff oTest.SomeStuff Set oTest = Nothing Command3_Err: MsgBox Err.Description & vbCrLf & Err.Number & _ vbCrLf & Err.Source End Sub
Server code:
Public Function SomeStuff() As Boolean On Error GoTo SomeStuff_Err Dim i As Integer i = 100 i = i / 0 Exit Function SomeStuff_Err: App.LogEvent Err.Description & " in " & _ "TestErrors::SomeStuff", _ vbLogEventTypeError Err.Raise vbObjectError + Err.Number, _ "TestErrors::SomeStuff", Err.Description End Function
As you can see from the server code, an illegal operation takes place—you can't divide by zero. The error handler logs the event and then uses the Err.Raise method to pass this error on to the client.
You can also use the Err.Raise method to trap incorrect user input. Look at MoreStuff, the following modified version of the server SomeStuff function:
Public Function MoreStuff(iVal As Integer) As Integer On Error GoTo MoreStuff_Err If iVal > 50 Then Err.Raise 23456, "TestErrors::MoreStuff", _ "Can't have a figure greater than 50" End If iVal = iVal / 0 Exit Function MoreStuff_Err: App.LogEvent Err.Description & " in " & _ "TestErrors::MoreStuff", _ vbLogEventTypeError Err.Raise vbObjectError + Err.Number, _ "TestErrors::MoreStuff", Err.Description End Function
This time, the Err.Raise method passes back a custom error code and description alerting the user of the invalid input.
Use the vbObjectError constant
Errors generated in OLE objects start at –2147221504. (Actually, OLE automation errors aren't negative numbers; they are unsigned long integers. But because VB doesn't support unsigned longs, they appear as a negative.) VB provides vbObjectError, an intrinsic constant for this value that you should add to both custom error codes and system error codes. However, if you add vbObjectError to an error code that is already of greater negative magnitude than –262144, you generate an "Overflow" error that masks the real error that occurred in the procedure. Because of this, it's best if your error handler adds vbObjectError only to positive numbers, as this snippet demonstrates:
SomeStuff_Err: App.LogEvent Err.Description & " in " & _ "TestErrors::SomeStuff", _ vbLogEventTypeError If Err.Number < 0 Then Err.Raise Err.Number, "TestErrors::SomeStuff", _ Err.Description Else Err.Raise vbObjectError + Err.Number, _ "TestErrors::SomeStuff", Err.Description End If
Where did the error occur?
Passing back the source of the error to the client application is very important. However, unless you take care writing your error handler, you can report the source as the last procedure to handle the error, rather than the one in which the error occurred. This example shows how this can happen:
Public Function SomeStuff() As Boolean On Error GoTo SomeStuff_Err Dim i As Integer i = 100 i = MoreStuff(i) Exit Function SomeStuff_Err: App.LogEvent Err.Description & " in " & _ "TestErrors::SomeStuff", _ vbLogEventTypeError If Err.Number < 0 Then Err.Raise Err.Number, _ "TestErrors::SomeStuff", Err.Description Else Err.Raise vbObjectError + Err.Number, _ "TestErrors::SomeStuff", Err.Description End If End Function Private Function MoreStuff(iVal As Integer) As Integer On Error GoTo MoreStuff_Err iVal = iVal / 0 Exit Function MoreStuff_Err: Err.Raise vbObjectError + Err.Number, _ "TestErrors::MoreStuff", Err.Description End Function
In this example, SomeStuff has called MoreStuff, in which an error is raised. The error is first handled by the error handler in MoreStuff, which reports the source of the error correctly. The error is then handled by SomeStuff 's error handler, which overwrites the original Source argument and reports it as being SomeStuff. You would wrongly assume that your problem was with the SomeStuff function.
If a system-generated error occurs, the source property of the Err object is set to the same value as the App.Title property. So you know that if the Err.Source property is the same as App.Title, the error has been generated in the current procedure, and you can safely use your own custom Source string. However, if the Err.Source property differs from the application title, the current Err.Source property should be passed on, as this snippet shows:
Dim ErrSrc As String If Err.Source <> App.Title Then ErrSrc = Err.Source Else Err.Src = App.Title & "::SomeStuff" End If
Don't forget to clean up before you leave
If your procedure exits unexpectedly, you run the risk of leaving objects that have been used in the procedure live. To prevent this, you should get into the habit of adding code to the beginning of your error handler to set all object variables that are declared in the procedure to Nothing. This way, whenever an error occurs, you will be sure that all objects are safely destroyed.