ABOUT Visual Basic Programmieren Programmierung Download Downloads Tips & Tricks Tipps & Tricks Know-How Praxis VB VBA Visual Basic for Applications VBS VBScript Scripting Windows ActiveX COM OLE API ComputerPC Microsoft Office Microsoft Office 97 Office 2000 Access Word Winword Excel Outlook Addins ASP Active Server Pages COMAddIns ActiveX-Controls OCX UserControl UserDocument Komponenten DLL EXE
Diese Seite wurde zuletzt aktualisiert am 28.05.2001

Diese Seite wurde zuletzt aktualisiert am 28.05.2001
Aktuell im ABOUT Visual Basic-MagazinGrundlagenwissen und TechnologienKnow How, Tipps und Tricks rund um Visual BasicAddIns für die Visual Basic-IDE und die VBA-IDEVBA-Programmierung in MS-Office und anderen AnwendungenScripting-Praxis für den Windows Scripting Host und das Scripting-ControlTools, Komponenten und Dienstleistungen des MarktesRessourcen für Programmierer (Bücher, Job-Börse)Dies&Das...

Themen und Stichwörter im ABOUT Visual Basic-Magazin
Code, Beispiele, Komponenten, Tools im Überblick, Shareware, Freeware
Ihre Service-Seite, Termine, Job-Börse
Melden Sie sich an, um in den vollen Genuss des ABOUT Visual Basic-Magazins zu kommen!
Informationen zur AVB-Web-Site, Kontakt und Impressum

Zurück...

Verfolgungsjagd

Zurück...

(-hg) mailto:hg_trackinstances@aboutvb.de

Eine der Grundregeln der Programmierung mit Objekten in Visual Basic besagt, dass alle Objekte, die einmal instanziert worden sind, auch wieder terminieren müssen. In der Regel sorgt Visual Basic von alleine dafür, dass zum Programmende alle noch vorhandenen Objekt-Instanzen terminieren. Aber, wie das immer so ist: keine Regel ohne Ausnahme.

Die erste Ausnahme stellen noch geladene Form-Objekte dar. In die Falle, dass zwar das Hauptfenster Ihrer Anwendung geschlossen und entladen worden ist, aber trotzdem aus unerfindlichen Gründen noch irgend welche anderen verborgenen Forms geladen sind, sind Sie sicher wie jeder Visual Basic-Entwickler bereits mehr als einmal hineingetappt. Die Folge ist, dass die Anwendung nur noch brutal über den Taskmanager abzuschießen ist. Nebenbei gesagt: Diese "unerfindlichen" Gründe gibt es eigentlich nicht - es kann eigentlich nur an Ihnen gelegen haben, dass Sie in Ihrem Code vergessen oder übersehen haben, jene(s) Form(s) zu entladen! Visual Basic geht nämlich davon aus, dass ein Programm erst dann tatsächlich beendet werden soll, wenn absolut kein Form mehr geladen ist. Das heißt genau, dass entweder nie irgend ein Form geladen worden ist, oder dass gerade das letzte noch vorhandene Form entladen wurde. Und erst wenn ein Form entladen worden ist, kann das zugehörige Form-Objekt terminieren und sein Terminated-Ereignis ausgelöst werden.

Eine andere Ausnahme stellen so genannte zirkuläre Referenzen dar. Kurz gesagt: Solche zirkulären Referenzen treten beispielsweise auf, wenn sich zwei Objekt-Instanzen jeweils gegenseitig referenzieren. Diese Situation tritt vor allem bei Eltern-Kind-Objekthierarchien auf, wenn sowohl ein Kind-Objekt noch eine Referenz auf sein Eltern-Objekt enthält, wie auch das Eltern-Objekt noch eine Referenz auf das Kind-Objekt. In einem solchen Fall wird zwar Ihre Anwendung von Visual Basic beendet, und auch die per Referenzen verbunden Objekte werden von VB schließlich noch ordentlich terminiert. Doch erfolgt diese Terminierung erst beim tatsächlichen Ende des Programms. Falls Sie schon lange vorher das Eltern- oder das Kind-Objekt loswerden wollen, etwa um Arbeitsspeicher und Ressourcen zurück zu gewinnen, müssen Sie eine zirkuläre Referenz selbst kontrolliert aufheben, etwa durch eine eigene Freigabe-Methode bei einem Kind-Objekt. In dieser wird ausdrücklich die Referenz auf das Eltern-Objekt freigegeben, statt auf das so vorläufig nicht wie erhofft eintreffende Terminate-Ereignis zu warten. Doch je komplexer ein Objekt-Modell und die Kreuz- und Quer-Referenzierungen untereinander werden, um so schwieriger wird es, die Kontrolle über alle notwendigen Freigaben zu behalten, und sicherzustellen, dass alle Freigabe-Aufrufe auch tatsächlich ausgeführt werden.

Ein sehr nützliches Hilfsmittel ist die im Folgenden vorgestellte Klasse bzw. Komponente TrackInstances, die im Debug-Modus in der Entwicklungsumgebung die Verfolgung von Objekt-Instanzen vereinfacht und Ihnen jederzeit den Stand der Instanzierungen einer jeden einzelnen Klasse und eines jedes einzelnen Forms mitteilen kann. Dazu können Sie auch noch die letzten geladenen Forms in einem Rutsch oder nacheinander manuell entladen, wenn diese das Beenden des Programms verhindern sollten, während an sich bereits sämtlicher Code des Programms abgearbeitet worden ist.

Die Klasse TrackInstances ist als globales Objekt (Instancing = 6 - GlobalMultiUse) in einer eigenen Komponente (ActiveX-DLL) angelegt. Sie brauchen lediglich in Ihrem Projekt bzw. in jedem Projekt einer Projekt-Gruppe einen Verweis auf diese Komponente anzulegen. Ob Sie die Komponente kompilieren oder ob Sie sie als Projekt in Projekt-Gruppen aufnehmen, bleibt sich gleich.

Nun braucht es nur noch ein wenig Disziplin Ihrerseits, in jeder Klasse, in jedem Form, in jedem UserControl usw. in deren Initialized- und Terminate-Ereignissen jeweils eine immer gleiche Code-Zeile einzufügen - im Initialized-Ereignis vor Ihrem weiteren Code als erste Zeile, im Terminate-Ereignis nach Ihrem weiteren Code dort als letzte Zeile:

Private Sub XYZ_Initialize()
  Debug.Assert Initialized(Me, App.Title)
  ' ... weiterer Code
End Sub

Private Sub XYZ _Terminate()
  ' ... weiterer Code
  Debug.Assert Terminated(Me, App.Title)
End Sub

Die Aufrufe in der Weise eines Argument zu Debug.Assert sorgen dafür, dass die Methoden Initialized und Terminated nur im Debug-Modus der Entwicklungsumgebung aufgerufen werden und nicht in die ausführbare Version kompiliert werden. Zudem sind die beiden Methoden-Aufrufe als Funktionen angelegt, die als Voreinstellung immer True zurückgegeben und so den Lauf des Programms nicht unterbrechen. Setzen Sie die Eigenschaft TrackDebugAssert dagegen ausdrücklich auf False, wird der Programmlauf nach jeder Rückkehr der Methoden-Aufrufe angehalten.

Mit der Eigenschaft TrackDebugPrint (Voreinstellung = False) legen Sie fest, ob jede Instanzierung und jede Terminierung einer Objekt-Instanz im Debug-Fenster gemeldet werden soll. Und über der Eigenschaft Tracking (Voreinstellung = True) schalten Sie das Instanzen-Tracking ausdrücklich ein oder aus.

Die reine Nachverfolgung der Instanzierungen und Terminierungen ist aber noch nicht alles, was diese Komponente leisten kann. So können Sie jederzeit den Programmlauf unterbrechen und die Methode TrackShow aus dem Debug-Fenster heraus aufrufen. Sie listet daraufhin dort die aktuelle Anzahl der Instanzierungen zusammen mit dem jeweiligen Klassen-Namen auf. Sie können auch optional ausdrücklich einen beliebigen Klassen-Namen als Parameter zu TrackShow angeben - dann wird nur die Anzahl der Instanzierungen dieser Klasse aufgelistet.

Sollte Ihre Anwendung im Debug-Modus in der Entwicklungsumgebung nicht beendet werden, weil noch verdeckte Forms geladen sind, können Sie über die Methode UnloadForms veranlassen (ebenfalls wieder im Direkt-Fenster), dass die noch geladenen Forms in der Reihenfolge ihrer ursprünglichen Instanzierung entladen werden. Sie können jedoch auch den optionalen Parameter OnlyFirst auf True setzen und damit angeben, dass nur das erste gefundene Form entladen werden soll. Oder sie geben im optionalen Parameter FormName den Namen eines bestimmten Forms an, dessen vorhandene Instanzen entladen werden sollen. Setzen Sie auch hierbei den Parameter OnlyFirst auf True, wird auch hier nur die erste gefundene Instanz eines Forms dieses Namens entladen.

Zu guter Letzt gibt es noch die Methode TrackObject. Sie gibt Ihnen aus der Sammlung der instanzierten Objekte eine beliebige Objekt-Instanz zurück, die Sie über den Klassen-Namen (bzw. Form-Namen usw.) und der Index der registrierten Instanz spezifizieren. Dann können Sie, auch wieder im Debug-Fenster, auf Methoden und Eigenschaften dieses Objekts zugreifen - etwa um näher in Erfahrung zu bringen, um welche Instanz es sich handelt, oder um es nach Belieben zu manipulieren:

TrackObject("Form1", 1).Show

In der Komponente werden übrigens keine Referenzen auf die instanzierten Objekte gespeichert, sondern nur die Objekt-Zeiger selbst, die zu jedem Objekt von der (nicht dokumentierten) Visual Basic-Funktion ObjPtr geliefert werden. Erst wenn zur Auflistung beim Aufruf von TrackShow, oder zum Entladen bei UnloadForms, oder beim Aufruf von TrackObject das betreffende Objekt selbst benötigt wird, wird eine - wie gewohnt verwendbare - Referenz davon angelegt. Allerdings geschieht dies ohne Erhöhung des internen Referenz-Zählers - die ansonsten immer im Hintergrund wirkenden COM- und VB-Mechanismen merken nichts von der Existenz solch einer zusätzlichen Referenz. Diese Referenz wird über einen kleinen Trick erzeugt - näheres dazu finden Sie in "Vom Zeiger zum Objekt".

Zum Abschluss sehen Sie nun noch den kompletten Code der Klasse TrackInstances der Komponente avbTrackInstances:

Private Declare Sub CopyMemory Lib "kernel32" _
 Alias "RtlMoveMemory" (dest As Any, source As Any, _
 ByVal bytes As Long)

Private mCounters As Collection

Private pTrackDebugAssert As Boolean
Private pTrackDebugPrint As Boolean
Private pTracking As Boolean

Public Property Get TrackDebugAssert() As Boolean
  TrackDebugAssert = pTrackDebugAssert
End Property

Public Property Let TrackDebugAssert _
 (New_TrackDebugAssert As Boolean)

  pTrackDebugAssert = New_TrackDebugAssert
End Property

Public Property Get TrackDebugPrint() As Boolean
  TrackDebugPrint = pTrackDebugPrint
End Property

Public Property Let TrackDebugPrint _
(New_TrackDebugPrint As Boolean)

  pTrackDebugPrint = New_TrackDebugPrint
End Property

Public Property Get Tracking() As Boolean
  Tracking = pTracking
End Property

Public Property Let Tracking(New_Tracking As Boolean)
  pTracking = New_Tracking
End Property

Public Function Initialized(Object As Object, _
 Optional AppTitle As String) As Boolean

  Dim nKey As String
  Dim nInstances As Collection
  
  If Not pTracking Then
    Initialized = True
    Exit Function
  End If
  Initialized = pTrackDebugAssert
  If StrPtr(AppTitle) = 0 Then
    nKey = TypeName(Object)
  Else
    nKey = AppTitle & "." & TypeName(Object)
  End If
  If pTrackDebugPrint Then
    Debug.Print "Instanziert: ", nKey
  End If
  If mCounters Is Nothing Then
    Set mCounters = New Collection
    Set nInstances = New Collection
    nInstances.Add ObjPtr(Object), CStr(ObjPtr(Object))
    mCounters.Add nInstances, nKey
  Else
    On Error Resume Next
    Set nInstances = mCounters(nKey)
    On Error GoTo 0
    If nInstances Is Nothing Then
      Set nInstances = New Collection
      mCounters.Add nInstances, nKey
    End If
    nInstances.Add ObjPtr(Object), CStr(ObjPtr(Object))
  End If
End Function

Public Function Terminated(Object As Object, _
 Optional AppTitle As String) As Boolean

  Dim nKey As String
  Dim nInstances As Collection
  
  If Not pTracking Then
    Terminated = True
    Exit Function
  End If
  Terminated = pTrackDebugAssert
  If StrPtr(AppTitle) = 0 Then
    nKey = TypeName(Object)
  Else
    nKey = AppTitle & "." & TypeName(Object)
  End If
  Set nInstances = mCounters(nKey)
  nInstances.Remove CStr(ObjPtr(Object))
  If nInstances.Count = 0 Then
    mCounters.Remove nKey
  End If
  If pTrackDebugPrint Then
    Debug.Print "Terminiert: ", nKey, nInstances.Count
  End If
  If mCounters.Count = 0 Then
    Set mCounters = Nothing
  End If
End Function

Public Sub TrackShow(Optional ObjTypeName As String, _
 Optional ByVal TrackDebugPrint As Boolean = True)

  Dim l As Long
  Dim nInstances As Collection
  
  If mCounters Is Nothing Then
    If pTrackDebugPrint Or TrackDebugPrint Then
      Debug.Print "Keine Referenzen vorhanden."
      Debug.Print
    End If
  Else
    If StrPtr(ObjTypeName) = 0 Then
      For l = 1 To mCounters.Count
        Debug.Print mCounters(l).Count, _
         TypeName(zPtrToObject(mCounters(l)(1)))
      Next 'l
    Else
      On Error Resume Next
      Set nInstances = mCounters(ObjTypeName)
      On Error Resume Next
      If pTrackDebugPrint Or TrackDebugPrint Then
        If nInstances Is Nothing Then
          Debug.Print _
           "Keine Referenzen von " & ObjTypeName & " vorhanden."
        Else
          Debug.Print nInstances.Count, ObjTypeName
        End If
        Debug.Print
      End If
    End If
  End If
End Sub

Public Function TrackObject(ObjTypeName As String, _
 ByVal Index As Long, Optional ByVal Trace As Boolean) _
 As Object

  If pTracking Then
    Set TrackObject = _
     zPtrToObject(mCounters(ObjTypeName)(Index))
    Debug.Assert Not Trace
  End If
End Function

Public Sub UnloadForms(Optional ByVal Trace As Boolean, _
 Optional ByVal OnlyFirst As Boolean, Optional FormName As String)
  If pTracking Then
    Debug.Assert Not Trace
    If OnlyFirst And CBool(StrPtr(FormName) = 0) Then
      If Not (mCounters Is Nothing) Then
        zUnloadFirstForm
      End If
    Else
      Do While Not (mCounters Is Nothing)
        If zUnloadFirstForm(OnlyFirst, FormName) Then
          Exit Do
        End If
      Loop
    End If
  End If
End Sub

Private Function zUnloadFirstForm(ByVal OnlyFirst As Boolean, _
 Optional FormName As String) As Boolean

  Dim nObject As Object
  Dim nInstances As Collection
  Dim l As Long
  
  For Each nInstances In mCounters
    For l = 1 To nInstances.Count
      Set nObject = zPtrToObject(nInstances(l))
      If TypeOf nObject Is Form Then
        If StrPtr(FormName) = 0 Then
          Unload nObject
          Exit Function
        Else
          If TypeName(nObject) = FormName Then
            Unload nObject
            zUnloadFirstForm = OnlyFirst
            Exit Function
          End If
        End If
      End If
    Next 'l
  Next
  zUnloadFirstForm = True
End Function

Private Sub Class_Initialize()
  pTrackDebugAssert = True
  pTracking = True
End Sub

Private Sub Class_Terminate()
  Me.TrackShow False
End Sub

Private Function zPtrToObject(ByVal iObj As Variant) As Object
  Dim nObj As Object
  
  If VarType(iObj) = vbLong Then
    CopyMemory nObj, CLng(iObj), 4
  Else
    Exit Function
  End If
  Set zPtrToObject = nObj
  CopyMemory nObj, 0&, 4
End Function

Das Projekt avbTrackInstances und Beispiel-Projekt (trackinstances.zip - ca. 6,7 KB)



Komponenten-Übersicht

Schnellsuche




Zum Seitenanfang

Copyright © 1999 - 2023 Harald M. Genauck, ip-pro gmbh  /  Impressum

Zum Seitenanfang

Zurück...

Zurück...