Quantcast
Channel: VBForums - CodeBank - Visual Basic 6 and earlier
Viewing all articles
Browse latest Browse all 1470

My CallStack Class is a mess (Working Demo)

$
0
0
CallStack Demo.zip

If you have MZTools or something like it you can create a macro that automatically inserts the callstack stuff into any procedure with the correct information.

I really like MZTools.

I forgot to add a dot in there. Still works but has one weird entry.

in chkLogCallsToFile_Click change the following:

change this: m_CallStacker.Add Me.NAME & "chkLogCallsToFile_Click(Private Sub)"
To this: m_CallStacker.Add Me.NAME & ".chkLogCallsToFile_Click(Private Sub)"

Just needs the dot first thing after the first quote.

Also left out the dot in the Form_Unload Sub.
Also left out the dot in Text1_Change

=====================

How It works:

I don't remember.

What it does:

It counts every call to every procedure that adds to the call stack. When the program exits (only if it's enabled at exit) it provides a list of every procedure called, how many times it was called and the deepest call stack.

Every single call is logged to file. The file is opened and when the max number of entries (specified in the cCallStack Initialize Sub) is reached it closes the file and opens a new one.

So if you look at one of the callstack files you see that the stack increases one call at a time and then backs out one call at a time until the stack has nothing left on it. (Edit: I just looked a callstack file and it doesn't look like I added logging when it backs out. I'll have to check it again.)

When the program exits on purpose the current active callstack file is closed.

if the program crashes when a file is open then we count on Windows to close the file and hopefully you'll be able to look at the last callstack in the last file and see the stack that led up to the crash.

In my real app, all the totals are put into an application log file. In the demo it's put into the immediate window.

If you do too much with the demo then all the stats will be scrolled off the top of the immediate window so you just want to type a little bit, click a few things and then exit.

You could also just write that to the Windows Clipboard instead and then paste it into a text document instead.

You will find that in the CloseLogFile Function in the ErrorHandler bas module.

Where this really shines is that if you're in the IDE and something is going on and you set a breakpoint AND you have some kind of API thing going on, such as changing the color of a toolbar or SSTab or whatever, the program might crash and bring down the entire IDE even if the API call wasn't the problem.

This allows you to keep running and figure out what's going on without hitting CTRL+ Break to pause program execution which can sometimes kill everything for reasons other than whatever actual problem you're trying to sort out.

I'd appreciate any feedback you can give me.

On now to the original post...

=====================

OK... that was click-bait. It's not a mess but man does it bring my app to its knees. For example, when I click the Communications Button (which saves whatever setting it had last such as ALL, Past Week, Past Two Weeks, This Quarter, etc.) and it's set to ALL then I can go get lunch and come back and it might be finished.

The problem is that if I wait to log stuff for whatever time-period and the app crashes or has some weird hard-to-track bug, then the callstack class is worthless if it doesn't save the current call stack.

So writing that to file is REALLY slow but it's the only way I know to make sure I have the information I need if something happens.

I only turn it on when I find a bug that I can't track using more conventional means.

I mean it's really bad. But I don't have a better answer.

And if you do and it means me having to rewrite all my code (there are over 8,000 procedures making calls to the callstack in this app) then I'll love/hate you for doing that to me. :)

This is the whole class:

Code:


Option Explicit


' // Constants, Types and Enums.


Public Enum CALL_STACK_ARRANGMENT

  idx_CallStackArrangment_CallDate = 0
  idx_CallStackArrangment_DateCall
  idx_CallStackArrangment_CallOnly
  idx_CallStackArrangment_DateOnly

End Enum


Public Enum LOG_PROCEDURE_CALLS

  idx_LogProcedureCalls_No = 0
  idx_LogProcedureCalls_Yes = 1

End Enum

' / Constants, Types and Enums.


' // Objects


  ' / Controls.

Private WithEvents mw_ArrangementComboBox As ComboBox

  ' / Controls.


' / Objects


' // Properties.

Private nArrangement As Long
Private sCalledProcedures() As String
Private nCalledProceduresCount() As Long
Private sCallID As String
Private sCallLog As String
Private iCallLogFileNum As Integer
Private rCallNumber As Double
Private sCallStack() As String
Private nCallStacksPerFile As Long
Private rCallStackTime() As Double
Private sDeepestCallStack() As String
Private rDeepestCallStackTime() As Double
Private nLogCalls As Long
Private sLogFolder As String
Private nMaxCallStackLog As Long

' / Properties.



Public Property Get ActiveCallStack() As String
Dim s As String

On Error GoTo errHandler

ActiveCallStack = vbNullString

If Not ArrayInitialized(sCallStack) Then GoTo CleanUp

s = "Call Stack:" & DBL_RETURN

s = s & CallText(sCallStack, rCallStackTime)

ActiveCallStack = s

CleanUp:

Exit Property

errHandler:
Dim nErrorHandlerResult As Long

nErrorHandlerResult = ErrorHandler(Error, Err, vbNullString, Me.NAME & ".ActiveCallStack(Public Property Get)")

Resume CleanUp

End Property
Public Property Get ActiveStackCount() As Long

On Error GoTo errHandler

ActiveStackCount = UBound(sCallStack) + 1

Exit Property

errHandler:

ActiveStackCount = 0

End Property
Public Function Add(ByVal ModuleAndProcedureName As String) As Long
Dim nResult As Long
Dim nErr As Long

' Returns Error Code.
On Error GoTo errHandler

nErr = 0

If ErrorHandler.TERMINAL_ERROR Then Exit Function

CallID = NextCallID

If DebugMode = idx_Debug_Off Then Exit Function

nResult = AppendCallStackString(ModuleAndProcedureName)
If nResult <> 0 Then Err.Raise nResult

nResult = IncrementProcedureCallCount(ModuleAndProcedureName)
If nResult <> 0 Then Err.Raise nResult

LogCallStack

CleanUp:

Add = nErr

Exit Function

errHandler:

nErr = Err

Resume CleanUp

End Function
Private Function AppendCallStackString(ByVal ModuleAndProcedureName As String) As Long
Dim nErr As Long
Dim n As Long
Static nMax As Long

' Returns Error Code.
On Error GoTo errHandler

nErr = 0

If ArrayInitialized(sCallStack) Then

  n = UBound(sCallStack) + 1

  ReDim Preserve sCallStack(n)
  ReDim Preserve rCallStackTime(n)

Else

  n = 0

  ReDim sCallStack(n)
  ReDim rCallStackTime(n)

End If

sCallStack(n) = ModuleAndProcedureName
rCallStackTime(n) = Timer

If n > nMax Then

  nMax = n

  sDeepestCallStack = sCallStack
  rDeepestCallStackTime = rCallStackTime

End If

CleanUp:

AppendCallStackString = nErr

Exit Function

errHandler:

n = 0

Resume Next

End Function
Public Property Get Arrangement() As CALL_STACK_ARRANGMENT

Arrangement = nArrangement

End Property
Public Property Let Arrangement(ByVal CallArrangement As CALL_STACK_ARRANGMENT)

nArrangement = CallArrangement

End Property
Public Property Set ArrangmentComboBox(ByRef ctlComboBox As ComboBox)

Set mw_ArrangementComboBox = ctlComboBox

PopulateList

End Property
Private Function ArrIndex(ByRef ArrayOfStrings() As String, ByRef vItem As Variant) As Long
Dim nResult As Long
Dim n As Long

' Returns Index if Item is found.
' Returns FAILED (-1) if not found.
' Strings are not case-sensitive.

On Error GoTo errHandler

nResult = FAILED

If Not ArrayInitialized(ArrayOfStrings) Then GoTo CleanUp

For n = LBound(ArrayOfStrings) To UBound(ArrayOfStrings)

  If ArrayOfStrings(n) = vItem Then

    nResult = n

    GoTo CleanUp

  End If

Next n

CleanUp:

ArrIndex = nResult

Exit Function

errHandler:
Dim nErrorHandlerResult As Long
Dim sError As String
Dim nErr As Long
Dim Parameters(2) As String

sError = Error
nErr = Err

Parameters(0) = ParameterArray_str(ArrayOfStrings, "ArrayOfStrings")
Parameters(1) = "vItem = " & vItem
Parameters(2) = "n = " & CStr(n)

nErrorHandlerResult = ErrorHandler(sError, nErr, ParameterString(Parameters), Me.NAME & ".ArrIndex(Public Function)")

Resume CleanUp

End Function
Private Function ArrayInitialized(ByRef ArrayOfStrings() As String) As Boolean

On Error GoTo errHandler

ArrayInitialized = False

If SafeArrayGetDim(ArrayOfStrings) <> 0 Then ArrayInitialized = True

Exit Function

errHandler:

ArrayInitialized = False

End Function
Public Property Get CalledProcedures() As String()

CalledProcedures = sCalledProcedures

End Property
Public Property Get CalledProceduresCount() As Long

' Returns number of Distinct Procedures that have been called.

CalledProceduresCount = ArrayUbound(nCalledProceduresCount) + 1

End Property
Public Property Get CalledProceduresCounts() As Long()

' Returns Array containing number of times each procedure was called.

CalledProceduresCounts = nCalledProceduresCount

End Property
Public Property Get CallStacksPerFile() As Long

CallStacksPerFile = nCallStacksPerFile

End Property
Public Property Let CallStacksPerFile(ByVal CallsPerFile As Long)

nCallStacksPerFile = CallsPerFile

End Property
Private Property Get CallText(ByRef Calls() As String, ByRef Times() As Double) As String
Dim sCall As String
Dim sTime As String
Dim s As String
Dim n As Long

For n = LBound(Calls) To UBound(Calls)

  sCall = Calls(n)
  sTime = Format(Times(n), "0.000")

  Select Case Arrangement

    Case idx_CallStackArrangment_CallDate

      s = s & sCall & vbTab & sTime

    Case idx_CallStackArrangment_DateCall

      s = s & sTime & vbTab & sCall

    Case idx_CallStackArrangment_CallOnly

      s = s & sCall & vbCrLf

    Case idx_CallStackArrangment_DateOnly

      s = s & sTime

  End Select

Next n

CallText = s & vbCrLf

End Property
Private Property Get CallID() As String

CallID = sCallID

End Property
Private Property Let CallID(ByVal ProcedureCallID As String)

sCallID = ProcedureCallID

End Property
Public Property Get CallLog() As String

CallLog = sCallLog

End Property
Private Property Let CallLog(ByVal FileSpec As String)

sCallLog = FileSpec

End Property
Public Property Get DeepestCallStack() As String
Dim s As String

On Error GoTo errHandler

DeepestCallStack = vbNullString

If Not ArrayInitialized(sDeepestCallStack) Then Exit Property

s = "Deepest Call Stack (" & UBound(sDeepestCallStack) + 1 & ")" & DBL_RETURN

s = s & CallText(sDeepestCallStack, rDeepestCallStackTime)

DeepestCallStack = s

Exit Property

errHandler:
Dim nErrorHandlerResult As Long

nErrorHandlerResult = ErrorHandler(Error, Err, vbNullString, Me.NAME & ".DeepestCallStack(Public Property Get)")

End Property
Public Function DeleteProcedureCall() As Long
Dim nErr As Long
Dim n As Long

' Returns Error Code.
On Error GoTo errHandler

nErr = 0

If DebugMode = idx_Debug_Off Then Exit Function

If ArrayInitialized(sCallStack) Then

  n = UBound(sCallStack)

Else

  n = 0

End If

n = n - 1

If n < 0 Then

  Erase sCallStack

Else

  ReDim Preserve sCallStack(n)

End If

CleanUp:

DeleteProcedureCall = nErr

Exit Function

errHandler:

nErr = Err

Resume Next

End Function
Public Function DestroyObjects() As Long

Set mw_ArrangementComboBox = Nothing

On Error Resume Next

Close iCallLogFileNum

End Function
Private Function IncrementProcedureCallCount(ByVal Item As String) As Long
Dim nIndex As Long
Dim nBound As Long

On Error GoTo errHandler

If DebugMode = idx_Debug_Off Then Exit Function

nIndex = ArrIndex(CalledProcedures, Item) ' Search CallStack to see if it contains Procedure (Item).

If nIndex >= 0 Then ' Procedure was found so increment number of times it has been called.

  nCalledProceduresCount(nIndex) = nCalledProceduresCount(nIndex) + 1

  Exit Function

End If

If ArrayInitialized(sCalledProcedures) Then

  nBound = UBound(sCalledProcedures) + 1 ' Procedure wasn't found so add it to CalledProcedures Array.

Else

  nBound = 0

End If

ReDim Preserve sCalledProcedures(nBound)

sCalledProcedures(nBound) = Item

ReDim Preserve nCalledProceduresCount(nBound)

nCalledProceduresCount(nBound) = 1

Exit Function

errHandler:

nBound = 0

Resume Next

End Function
Public Property Get LogCalls() As Long

LogCalls = nLogCalls

End Property
Public Property Let LogCalls(ByVal LogAllCalls As Long)

nLogCalls = LogAllCalls

End Property
Private Function LogCallStack() As Long
Dim nErr As Long
Dim s As String
Static rTotal As Double

' Returns Error Code.
On Error GoTo errHandler

If ErrorHandler.TERMINAL_ERROR Then Exit Function

If LogProcedureCallStack = vbUnchecked Then Exit Function

If rTotal = 0 Then rTotal = 1

If (rTotal Mod CallStacksPerFile = 0) Or iCallLogFileNum = 0 Then

  StartCallStackLog

End If

s = CallID & vbCrLf & Join(sCallStack, vbCrLf)

Print #iCallLogFileNum, vbNullString
Print #iCallLogFileNum, s

CleanUp:

LogCallStack = nErr

rTotal = rTotal + 1

Exit Function

errHandler:

nErr = Err

Resume CleanUp

End Function
Private Property Get LogFolder() As String

LogFolder = sLogFolder

End Property
Private Property Let LogFolder(ByVal FolderSpec As String)

sLogFolder = FolderSpec

End Property
Public Property Get LogProcedureCalls(ByVal ObjectName As String) As DEBUG_MODE
Dim RST As DAO.Recordset
Dim SQL As String
Dim nRecordcount As Long

SQL = "SELECT * FROM ObjectLogging WHERE ObjectName=" & AddSingleQuotes(ObjectName)
nRecordcount = OpenRST(RST, SQL, idx_Recordset_Dynaset)

With RST

  If nRecordcount = 0 Then

    .AddNew

      .Fields("ObjectName") = ObjectName
      .Fields("LogProcedureCalls") = 1

    .Update

    LogProcedureCalls = idx_Debug_On

  Else

    LogProcedureCalls = .Fields("LogProcedureCalls")

  End If

End With

RecordsetClose RST

End Property
Public Property Let LogProcedureCalls(ByVal ObjectName As String, DebugMode As DEBUG_MODE)
Dim RST As DAO.Recordset
Dim SQL As String
Dim nRecordcount As Long

SQL = "SELECT * FROM ObjectLogging WHERE ObjectName=" & AddSingleQuotes(ObjectName)
nRecordcount = OpenRST(RST, SQL, idx_Recordset_Dynaset)

With RST

  If nRecordcount = 0 Then

    .AddNew

      .Fields("ObjectName") = ObjectName

  Else

    .Edit

  End If

      .Fields("LogProcedureCalls") = DebugMode

  .Update

End With

RecordsetClose RST

End Property
Public Property Get MaxCallStackLog() As Long

MaxCallStackLog = nMaxCallStackLog

End Property
Public Property Let MaxCallStackLog(ByVal MaxStacksLogged As Long)

nMaxCallStackLog = MaxStacksLogged

End Property
Public Property Get NAME() As String

NAME = "cCallStack"

End Property
Private Property Get NextCallID() As String

rCallNumber = rCallNumber + 1

NextCallID = AirfieldApp.SessionID & CHAR_SPACE & rCallNumber

End Property
Private Function PopulateList() As Long
Dim nErr As Long

' Returns Error Code.
On Error GoTo errHandler

nErr = 0

If mw_ArrangementComboBox Is Nothing Then GoTo CleanUp

With mw_ArrangementComboBox

  .Clear

  .AddItem "Procedure Call - Date"
  .Itemdata(.NewIndex) = idx_CallStackArrangment_CallDate
 
  .AddItem "Date - Procedure Call"
  .Itemdata(.NewIndex) = idx_CallStackArrangment_DateCall

  .AddItem "Procedure Call Only"
  .Itemdata(.NewIndex) = idx_CallStackArrangment_CallOnly

  .AddItem "Date Only"
  .Itemdata(.NewIndex) = idx_CallStackArrangment_DateOnly

End With

ListIndexFromItemData mw_ArrangementComboBox, Arrangement

CleanUp:

PopulateList = nErr

Exit Function

errHandler:
Dim nErrorHandlerResult As Long

nErr = Err

nErrorHandlerResult = ErrorHandler(Error, nErr, vbNullString, Me.NAME & ".PopulateList(Private Function)")

Resume CleanUp

End Function
Private Function StartCallStackLog() As Long
Dim nErr As Long

' Returns Error Code.
On Error GoTo errHandler

nErr = 0

Close #iCallLogFileNum

If DebugMode = idx_Debug_Off Then GoTo CleanUp

CallLog = LogFolder & "Call Stacks " & AirfieldApp.SessionID & CHAR_SPACE & DateTimeSerial & ".txt"

iCallLogFileNum = FreeFile

Open CallLog For Output As #iCallLogFileNum

CleanUp:

StartCallStackLog = nErr

Exit Function

errHandler:
Dim nErrorHandlerResult As Long

nErr = Err

nErrorHandlerResult = ErrorHandler(Error, nErr, vbNullString, Me.NAME & ".StartCallStackLog(Private Function)")

Resume CleanUp

End Function
Public Property Get TotalCalledProceduresCount() As Long

TotalCalledProceduresCount = SumArray(nCalledProceduresCount)

End Property
Public Function TotalCalls() As String
Dim s As String
Dim n As Long
Dim rCount As Double

On Error GoTo errHandler

If ArrayInitialized(CalledProcedures) Then

  s = "Procedure Call Counts: " & DBL_RETURN

  For n = LBound(CalledProcedures) To UBound(CalledProcedures)

    s = s & Format(nCalledProceduresCount(n), "000000000") & vbTab & sCalledProcedures(n) & vbCrLf

    rCount = rCount + nCalledProceduresCount(n)

  Next n

  s = s & vbCrLf & vbTab & "Procedures Called: " & UBound(CalledProcedures) + 1 & vbCrLf

Else

  s = "Procedure Call Counts: " & vbCrLf

  s = s & vbCrLf & vbTab & "Procedures Called: Logging not Active." & vbCrLf

End If

If rCount Then

  s = s & vbCrLf & vbTab & "Total Procedure Calls: " & rCount & DBL_RETURN

Else

  s = s & vbCrLf & vbTab & "Total Procedure Calls: Logging not Active."

End If

s = s & DeepestCallStack

TotalCalls = s

Exit Function

errHandler:
Dim nErrorHandlerResult As Long

nErrorHandlerResult = ErrorHandler(Error, Err, vbNullString, Me.NAME & ".TotalCalls(Public Function)")

End Function
Private Sub mw_ArrangementComboBox_Change()

mw_ArrangementComboBox_Click

End Sub
Private Sub mw_ArrangementComboBox_Click()

Arrangement = Itemdata(mw_ArrangementComboBox)

End Sub
Private Sub Class_Initialize()

Arrangement = idx_CallStackArrangment_DateCall

LogFolder = AirfieldApp.LogFolder

CallStacksPerFile = 10000

StartCallStackLog

End Sub

cCallStacker Class:

Code:

Option Explicit


' Eliminates need for each Procedure to call DeleteProcedureCall.
' DeleteProcedureCall is called automatically when instance of this class goes out of scope.


' // Constants, Types and Enums.

Private Const NAME As String = "cCallStacker"

' / Constants, Types and Enums.


Public Sub Add(ByRef ProcedureInfo As String)

If DebugMode = idx_Debug_Off Then Exit Sub

CallStack.Add ProcedureInfo

End Sub
Private Sub Class_Terminate()

If DebugMode = idx_Debug_Off Then Exit Sub

CallStack.DeleteProcedureCall

End Sub


Usage:

Code:

Private Sub SomeSub()
dim m_CallStacker As New cCallStacker

m_CallStacker.Add (name of module) & ".SomeSub(Private Sub)"

If This Then

  DoThat

Else

  DontDoItImNotTheBossOfYou

End If

End Sub

Attached Files

Viewing all articles
Browse latest Browse all 1470

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>