QuickStart: Reading the callstack

What is a call stack?

Let's take a look at the VB call stack window:

VBE CallStack picture


This window shows you the procedure that we’re in (the top item), and the previous function calls that occurred to get there.

In other words, a call stack provides you with the path that was taken to get to where you are now.

Sadly, it is not normally possible to access the call stack in VBA (nor VB6). However, with vbWathdog, this is now a possibility...

Displaying the callstack in the error dialogs

The error dialogs provided by vbWatchdog have built in support for displaying the call stack:

Please review the Customizing the Error Dialog document and also the Sample.MDB for further information.  The remainder of this document focuses on programmatically accessing the callstack.

How to read the call stack at runtime programmatically

The ErrEx.CallStack property returns an object that gives you programmatic access to the VB call stack details at runtime from within your global error handler.  The object returned relates only the error being handled.

Similarly, the ErrEx.LiveCallStack property returns an object that gives you access to the live VB call stack details at runtime.   This object can be used outside of your global error handler.

The objects returned from these properties expose two methods and six properties that relate directly to the call stack:

.FirstLevel(method): puts you at the top of the stack i.e. the procedure that caused the error.
.NextLevel(method): moves you to the next stack level, returns False if at the end of stack.
.ProcedureName(read-only property): returns the current stack item procedure name, as a string.
.ModuleName(read-only property): returns the current stack item module name, as a string.
.ProjectName(read-only property): returns the current stack item project name, as a string.
.LineNumber(read-only property): returns the current stack item source code line number
see QuickStart: Automatic line numbering
.LineCode(read-only property): returns the current stack item line of source code as a string
see QuickStart: Automatic line numbering
.HasActiveErrorHandler(read-only property): indicates whether the procedure has an active error handler (On Error Goto X, or On Error Resume Next) or not.  This is useful for logging propagating errors - see QuickStart: Error Propagation
.ProjectFilename(read-only property): returns the VB project filename associated with the callframe.
.ProjectConditionalCompilationArgs(read-only property): returns the compiler conditional arguments associated with the callframe.
.ProjectIsCompiled [VBA only](read-only property): returns a boolean value indicating the compilation state of the project associated with the callframe.
.ProjectIsSaved [VBA only](read-only property): returns a boolean value indicating the save state of the project associated with the callframe.
.VBEProject [VBA only](read-only property): returns the VBE object (from the Extensibility Library) associated with the callframe.

Furthermore, the object also exposes the VariablesInspector method which returns a class object for enumerating through the local variables that have been declared in the procedure at the current stack level.

Example using ErrEx.Callstack

Let's look at a way of logging the call stack to a log file:

Public Sub LogErrorToFile()
    
    Dim FileNum As Long
    Dim LogLine As String
    Dim FilePath As String
    
    On Error Resume Next ' If this procedure fails, something fairly major has gone wrong.

    ' We will write to a simple text file called SampleErrorLog in our MyDocuments folder
    FilePath = CreateObject("WScript.Shell").SpecialFolders("MYDOCUMENTS") & "\SampleErrorLog.txt"

    FileNum = FreeFile
    Open FilePath For Append Access Write Lock Write As FileNum

        Print #FileNum, Now() & " - " & CStr(ErrEx.Number) & " - " & CStr(ErrEx.Description)
            
        'We will separate the call stack onto separate lines in the log
	With ErrEx.CallStack
            Do
                Print #FileNum, "       --> " & .ProjectName & "." & _
                                .ModuleName & "." & _
                                .ProcedureName & ", " & _
                                "#" & .LineNumber & ", " & _
                                .LineCode & vbCrLf
            Loop While .NextLevel
	End With

    Close FileNum

End Sub

The above Do-loop is iterating through the call stack, logging to a file - outputting like this:

27/08/2008 12:30:00 - 11 - Division by zero
       --> MyVBAProject.Module1.FnC, #1, Debug.Print 1 / 0
       --> MyVBAProject.Module1.FnB, #2, Call FnC
       --> MyVBAProject.Module1.FnA, #2, Call FnB
Take a look at the VariablesInspector guide where we enhance this logging further to include a dump of all the variables at each procedure stack level.

Example using ErrEx.LiveCallstack

If you don't wish to have a global error handler routine, and instead just want to read the LiveCallstack details to enhance your existing code with minimal changes, you can enable vbWatchdog without specifying an "OnError" handler.  You do this by calling ErrEx.Enable with a blank parameter:

    Call ErrEx.Enable("")

Then, for example, you could use normal local error handling like so:

Public Sub Test()

    On Error Goto ErrorHandler

    Debug.Print 1/0

    Exit Sub

ErrorHandler:
    MsgBox "Error occurred in: " & _
            ErrEx.LiveCallStack.ModuleName & "." & ErrEx.LiveCallStack.ProcedureName

Exit Sub

As you can see, this gives you the opportunity to stop hard-coding the values and thus makes maintenance easier without having to implement any major changes to your code.