Content Supported by Sourcelens Consulting
Attribute VB_Name = "modFriends"
Option Explicit
' Constants for debug functions.
Global Const DEBUGTOKEN_DebugID = 1
Global Const DEBUGTOKEN_ClassName = 2
' The user-defined type used by the
' Friend member demo.
Public Type udtDEMO
intA As Integer
lngB As Long
strC As String
End Type
' timeGetTime is used by the Implements
' ----------- demo, and by the
' object lifetime debug code in this
' module.
Declare Function timeGetTime Lib "winmm.dll" () As Long
' Storage for the global collection
' for debugging object lifetimes.
' Used by DebugInit, DebugTerm,
' and DebugShow procedures (below).
Private mcolDebug As New Collection
' DEBUGGING PROCEDURES
'
' DebugInit
' DebugTerm
' DebugShow
'
' All objects implement the IDebug
' interface, and support it by
' calling DebugInit(Me) in their
' Initialize events, and DebugTerm Me
' in their Terminate events. You can
' use DebugShow in the Immediate window
' to list an active object, all active
' objects, or all objects of a class.
'
' All of the objects in this project are
' set up to use these functions.
'
' -------------------------------------
' DebugInit is called by each object,
' --------- in its Initialize event.
' DebugInit adds a debug string for the
' object to the global collection, and
' returns a unique DebugID for the
' object. The method optionally shows
' the debug string in the Immediate
' window (default is True).
'
Public Function DebugInit(ByVal obj As Object, _
Optional ByVal ShowImmediate As Boolean = True) As Long
Dim lngDebugID As Long
Dim strDebug As String
' Get a unique ID number.
lngDebugID = GetDebugID
' The debug string kept for each
' object shows the DebugID, the
' class name of the object, and
' the time it was initialized
' (number of seconds since the
' first debug object was created,
' expressed as a Double, with the
' milliseconds as the fractional
' part).
strDebug = lngDebugID & " " _
& TypeName(obj) _
& " (created at " & DebugTime & ")"
'
' Add the string to the collection,
' using the unique ID as a key.
mcolDebug.Add strDebug, CStr(lngDebugID)
'
' The default is to show the debug
' string in the Immediate window.
If ShowImmediate Then Debug.Print strDebug
'
' Return the DebugID. The object must
' store this as part of the
' implementation of IDebug.
DebugInit = lngDebugID
End Function
' DebugTerm is called by each object,
' --------- in its Terminate event.
' DebugTerm removes the object's
' debug string from the global
' collection, and optionally (default
' is True) shows the debug string in
' the Immediate window.
'
Public Sub DebugTerm(ByVal obj As Object, _
Optional ByVal ShowImmediate As Boolean = True)
Dim idbg As IDebug
On Error Resume Next
'
' Get a reference to the object's
' IDebug interface.
Set idbg = obj
If Err.Number <> 0 Then
MsgBox TypeName(obj) & " doesn't implement IDebug; can't record termination.", , "DebugTerm"
Exit Sub
End If
'
' The default is to show the debug
' string in the Immediate window.
If ShowImmediate Then Debug.Print _
mcolDebug(CStr(idbg.DebugID)) _
& " (Term at " & DebugTime & ")"
'
' Remove the string from the
' collection.
mcolDebug.Remove CStr(idbg.DebugID)
End Sub
' DebugShow displays the debug string(s)
' --------- for the entire list of
' active objects, for all active objects
' of a class, or for a particular object.
' Call DebugShow from the Immediate
' window with no argument (lists all),
' a class name (lists all of that class),
' an object reference (lists that
' object), or the DebugID of an object
' (lists that object).
'
Public Sub DebugShow(Optional ByVal What As Variant)
Dim vnt As Variant
Dim idbg As IDebug
On Error GoTo NoShow
' If no argument is supplied, display
' all active objects. (It would be
' useful to have an optional second
' parameter Filename that would let
' you dump this to a file; or perhaps
' it should dump to the Clipboard.)
If IsMissing(What) Then
What = "<All>"
For Each vnt In mcolDebug
Debug.Print vnt
Next
'
' If an object is supplied, use its
' DebugID to look up its debug
' string.
ElseIf IsObject(What) Then
On Error Resume Next
'
' Get a reference to the object's
' IDebug interface.
Set idbg = What
If Err.Number <> 0 Then
MsgBox TypeName(What) & " doesn't implement IDebug; can't show debug record.", , "DebugShow"
Exit Sub
End If
'
Debug.Print mcolDebug(CStr(idbg.DebugID))
'
' If a number is supplied, assume it's
' a DebugID and use it to look up
' the string.
ElseIf IsNumeric(What) Then
Debug.Print mcolDebug(CStr(What))
'
' If it's not a number, assume it's
' a string containing the class
' name; display all objects with
' that class name.
Else
For Each vnt In mcolDebug
If What = GetDebugToken(vnt, DEBUGTOKEN_ClassName) Then
Debug.Print vnt
End If
Next
End If
Exit Sub
NoShow:
If IsObject(What) Then
MsgBox "Unable to display information. Is this object set up for debugging?", , "DebugShow"
Else
MsgBox "Unable to display information for " _
& What & ". Is this object set up for debugging?", , "DebugShow"
End If
End Sub
' GetDebugString returns an object's
' -------------- string from the global
' collection.
'
Public Function GetDebugString(ByVal obj As Object) As String
Dim idbg As IDebug
On Error Resume Next
'
' Get a reference to the object's
' IDebug interface.
Set idbg = obj
GetDebugString = mcolDebug(CStr(idbg.DebugID))
End Function
' GetDebugID is used to assign each object
' ---------- a unique ID number, for
' debugging purposes.
Public Function GetDebugID() As Long
Static lngLastID As Long
lngLastID = lngLastID + 1
GetDebugID = lngLastID
End Function
' GetDebugToken parses the debug string
' ------------- for an object and
' returns the requested token. Tokens
' are separated by single spaces.
' (1) DebugID
' (2) class name
'
' There are other tokens, but they're
' kind of a jumble.
'
Public Function GetDebugToken( _
ByVal DebugString As String, _
ByVal TokenNumber As Integer) As String
Dim inx1 As Long
Dim inx2 As Long
Dim ct As Integer
If TokenNumber <= 0 Then
Err.Raise vbObjectError + 1060, , _
"Bad token number in GetDebugToken"
Else
inx2 = 1
For ct = 1 To TokenNumber
inx1 = inx2
inx2 = InStr(inx1, DebugString, " ")
If inx2 = 0 Then Exit For
Next
If inx2 = 0 Then
GetDebugToken = ""
Else
GetDebugToken = Mid$(DebugString, inx1 + 1, inx2 - inx1)
End If
End If
End Function
' DebugTime uses the timeGetTime API to
' --------- get milliseconds since
' the computer was booted. This is
' converted to a Double containing the
' number of seconds since the first
' debug object was created (s.mmm),
' using the first time this function
' was called as the base time. (This
' makes the time values more useful
' than the raw number of milliseconds
' since the last boot, which (1) tends
' to be a very large number, and (2) can
' be negative, as explained below.)
'
Public Function DebugTime() As Double
Static timeBase As Double
Dim timeCurrent As Double
If timeBase = 0 Then
' Initialize the base time. (The
' loop allows for the fact that
' the time returned by timeGetTime
' can pass through zero again, if
' the computer is left running
' long enough.)
Do While timeBase = 0
timeBase = timeGetTime
Loop
'
' The value returned by timeGetTime
' can be negative (see note
' below) if the computer has
' been running long enough.
' Correct for this.
If timeBase < 0 Then
timeBase = timeBase + 4294967296#
End If
End If
'
timeCurrent = timeGetTime
'
' Correct for negative value, if
' necessary.
If timeCurrent < 0 Then
timeCurrent = timeCurrent + 4294967296#
End If
'
' Handle the case where timeGetTime
' rolls over to zero.
If timeCurrent < timeBase Then
DebugTime = (timeCurrent + 4294967296# - timeBase) / 1000#
Else
DebugTime = (timeCurrent - timeBase) / 1000#
End If
End Function
' ----------- timeGetTime -----------
' The number of milliseconds since
' last boot is an unsigned four-byte
' binary integer, which means it can
' get bigger than a Long can hold.
' When it passes the largest positive
' number a Long can hold, 2147483647,
' it appears to Basic as if the
' number has 'rolled over' and gone
' negative. Once it has rolled over,
' it continues increasing -- moving
' from the largest negative number a
' Long can hold up to zero, and then
' into positive numbers again.
'
' This creates a 'sawtooth' pattern,
' and it works just fine for time
' differences (which is what
' DebugTime is calculating), except
' for that awkward moment when the
' rollover happens.
'
' DebugTime solves this problem by
' putting the number into a larger
' container -- a Double. If the
' number is negative, it can be
' turned into the number it should
' have been by adding 4294967296.