views:

852

answers:

6

I have VB6 application , I want to put some good error handling finction in it which can tell me what was the error and exact place when it happened , can anyone suggest the good way to do this

+11  A: 

First of all, go get MZTools for Visual Basic 6, its free and invaluable. Second add a custom error handler on every function (yes, every function). The error handler we use looks something like this:

On Error GoTo {PROCEDURE_NAME}_Error

{PROCEDURE_BODY}

    On Error GoTo 0
    Exit {PROCEDURE_TYPE}

{PROCEDURE_NAME}_Error:

   LogError "Error " & Err.Number & " (" & Err.Description & ") in line " & Erl & _
            ", in procedure {PROCEDURE_NAME} of {MODULE_TYPE} {MODULE_NAME}"

Then create a LogError function that logs the error to disc. Next, before you release code add Line Numbers to every function (this is also built into MZTools). From now on you will know from the Error Logs everything that happens. If possible, also, upload the error logs and actually examine them live from the field.

This is about the best you can do for unexpected global error handling in VB6 (one of its many defects), and really this should only be used to find unexpected errors. If you know that if there is the possibility of an error occurring in a certain situation, you should catch that particular error and handle for it. If you know that an error occurring in a certain section is going to cause instability (File IO, Memory Issues, etc) warn the user and know that you are in an "unknown state" and that "bad things" are probably going happen. Obviously use friendly terms to keep the user informed, but not frightened.

Kris Erickson
Kris--I wish I could give you two upvotes for that answer. Very good answer!
Onorio Catenacci
Why do you add an On Error Goto 0 before the Exit? My understanding is that when the Exit (Function|Sub) occurs the current error handler loses scope automatically.
Darrel Miller
Also, assuming the original poster does what you said, any calling routine is going to be oblivious to the fact that an error occurred and will continue processing. How do you recover from the error?
Darrel Miller
The On Error Goto 0 isn't necessary (I think it was a code optimization for VB5, but I can't remember), it was just part of the boiler plate code. Like I stated in the answer, this is just to handle unexpected errors. You should code for any errors that you can possibly expect.
Kris Erickson
+1, but I would also suggest throwing the error back to the caller in the error handler. Otherwise the caller will carry on in ignorance that the routine hasn't worked and might cause worse problems. Event handlers obviously shouldn't throw or they'll crash the app.
MarkJ
When you rethrow the error you can also use maero's idea to build in a stack trace. If the top-level event handlers show a message box, this means you have the stack trace right there without needing to view the logs. I've also noticed your current answer doesn't tell the user things have gone wrong
MarkJ
A: 

Use the On Error statement and the Err object.

Robert S.
+1  A: 
ON ERROR GOTO

and the

Err

object.

See this discussion.

Joe Skora
+1  A: 

Yes, take Kris's advice and get MZTools.

You can add line numbers to section off areas of complex procedures, which ERL will report in the error handler, to track down which area is causing the error.

10
    ...group of statements
20
    ...group of statements
30
    ...and so on
Gordon Bell
+2  A: 

a simple way without additional modules, useful for class modules:

pre-empt each function/subs:
On Error Goto Handler
handler/bubbleup:
Handler:
Err.Raise Err.Number, "(function_name)->" & Err.source, Err.Description

voila, ghetto stack trace.

maero
+1 Nice idea. And you can add this boilerplate automatically with the free MZTools
MarkJ
+1  A: 

I use a home-grown Error.bas module to make reporting and re-raising less cumbersome.

Here's its contents (edited for length):

Option Explicit

Public Sub ReportFrom(Source As Variant, Optional Procedure As String)
    If Err.Number Then
        'Backup Error Contents'
        Dim ErrNumber As Long: ErrNumber = Err.Number
        Dim ErrSource As String: ErrSource = Err.Source
        Dim ErrDescription As String: ErrDescription = Err.Description
        Dim ErrHelpFile As String: ErrHelpFile = Err.HelpFile
        Dim ErrHelpContext As Long: ErrHelpContext = Err.HelpContext
        Dim ErrLastDllError As Long: ErrLastDllError = Err.LastDllError
    On Error Resume Next
        'Retrieve Source Name'
        Dim SourceName As String
        If VarType(Source) = vbObject Then
            SourceName = TypeName(Source)
        Else
            SourceName = CStr(Source)
        End If
        If LenB(Procedure) Then
            SourceName = SourceName & "." & Procedure
        End If
        Err.Clear
        'Do your normal error reporting including logging, etc'
        MsgBox "Error " & CStr(ErrNumber) & vbLf & "Source: " & ErrSource & vbCrLf & "Procedure: " & SourceName & vbLf & "Description: " & ErrDescription & vbLf & "Last DLL Error: " & Hex$(ErrLastDllError)
        'Report failure in logging'
        If Err.Number Then
            MsgBox "Additionally, the error failed to be logged properly"
            Err.Clear
        End If
    End If
End Sub

Public Sub Reraise(Optional ByVal NewSource As String)
    If LenB(NewSource) Then
        NewSource = NewSource & " -> " & Err.Source
    Else
        NewSource = Err.Source
    End If
    Err.Raise Err.Number, NewSource, Err.Description, Err.HelpFile, Err.HelpContext
End Sub

Reporting an error is as simple as:

Public Sub Form_Load()
On Error Goto HError
    MsgBox 1/0
    Exit Sub
HError:
    Error.ReportFrom Me, "Form_Load"
End Sub

Reraising an error is as simple as calling Error.Reraise with the new source.

Although it is possible to retrieve the Source and Procedure parameters from the call stack if you compile with symbolic debug info, it's not reliable enough to use in production applications

rpetrich
Nice idea but you should really put Exit Sub above "HError:" in your example Form_Load routine.
MarkJ
Good catch MarkJ! For the sake of completeness, I've added that
rpetrich
As I pointed out in another incarnation of this question:I see one big drawback here. Now, if I do this, all runtime errors are handled. Debugger will not stop application at error location. Instead it will stop inside error handler in some other procedure down the stack. So this method helps with a I-have-no-debugger-in-production-environment scenario but breaks normal work with VB6 IDE.
Tomek Szpakowicz
For debugging in the IDE, one should always set it to break on all errors (of course, that assumes normal program flow does not generate errors)
rpetrich