Die Lebensdauer einer Objekt-Instanz hängt davon ab, wie viele
Referenzen auf sie existieren. Jede zusätzliche Referenz auf eine
Instanz, die etwa in einer in einer Objekt-Variablen oder einer
Collection abgelegt wird, erhöht den Referenzzähler dieser
Instanz. Dieser wird von Visual Basic im Verborgenen verwaltet und
ist eigentlich nicht zugänglich. Und erst wenn die letzte Referenz
auf die Objekt-Instanz wieder freigegeben ist (Objekt-Variable
gleich Nothing gesetzt, Objekt aus einer Collection entfernt), der
Referenzzähler also wieder 0 ist, kann ein Objekt
terminieren und (bei Forms, VB-Klassen oder UserControls usw.) das
Terminate-Ereignis ausgelöst werden. Genau so kann ein
ActiveX-Server (DLL, EXE oder OCX) auch nicht terminieren kann, wenn
irgendwo auf der Client-Seite (Ihre Anwendung) noch eine
Objekt-Referenz existiert.
Bei komplexeren Anwendungen kann es leicht passieren, dass Sie
eine Objekt-Referenz ein wenig aus den Augen verloren haben.
Interessant wäre es da sicherlich, den Referenz-Zähler überwachen
zu können - lediglich die Initialize- und Terminate-Ereignisse eine
Debug-Meldung im Direkt-Fenster ausgeben zu lassen, hilft in Bezug
darauf nämlich nicht sonderlich viel.
Wie gesagt: Visual Basic verbirgt zwar offiziell den
Referenz-Zähler vor uns - doch er ist trotzdem vorhanden. Er steht
nämlich im Arbeitsspeicher genau hinter der Stelle, an der Visual
Basic die Referenz auf die "Ur-Instanz", nämlich auf die
bei allen COM-Objekten vorhandene Schnittstelle IUnknown abgelegt
hat.
Die Speicher-Adresse der Schnittstellen-Referenz rückt Visual
Basic freiwillig über die (allerdings nicht dokumentierte) Funktion
ObjPtr heraus. Den Inhalt der Speicherstelle dahinter können wir
jedoch nicht direkt lesen. Doch mit der API-Funktion CopyMemory
(die eigentlich RtlMoveMemory heißt, jedoch meistens unter diesem
Namen deklariert wird) können wir den dort stehenden Wert in eine
eigene Variable des Datentyps Long kopieren (siehe auch "Vom
Zeiger zur Variablen".
Der Hilfsfunktion GetRefCount übergeben Sie eine Referenz auf
das betreffende Objekt und erhalten die Zahl der Referenzen zurück.
Sollten Sie eine Objekt-Referenz übergeben, die nur ein Nothing
enthält, versucht die Funktion gar nicht erst, den Referenz-Zähler
zu kopieren. Sie wird vielmehr sogleich abgebrochen und der
Rückgabewert ist folglich 0.
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _
(Dest As Any, Source As Any, ByVal Bytes As Long)
Public Function GetRefCount(Object As IUnknown) As Long
Dim nRefCount As Long
If Object Is Nothing Then
Exit Function
Else
CopyMemory nRefCount, ByVal (ObjPtr(Object)) + 4, 4
GetRefCount = nRefCount - 2
End If
End Function
Sie mögen sich vielleicht darüber wundern, warum der kopierte
Referenz-Zähler noch um 2 vermindert wird. Wenn Sie
diese Zeile einmal versuchsweise auskommentieren, werden Sie
feststellen, dass die Zählung offensichtlich nicht stimmt. Sie
scheint um eben diese 2 zu hoch zu sein. Der Grund
hierfür ist jedoch bei näherer Betrachtung klar: Bei der Übergabe
an unsere Funktion wird der Referenz-Zähler bereits um 1
erhöht. Ein weiteres Mal wird er bei der Übergabe an die Funktion
ObjPtr erhöht. Diese beiden Zählungen müssen wir natürlich
wieder abziehen, um eine korrekte Zählung zu erhalten.
Ein interessantes, mir aber zur Zeit noch unerklärliches
Phänomen tritt in Erscheinung, wenn die Referenz-Zählung für ein
Form aus dem Code-Modul dieses Forms heraus aufgerufen wird. Die
Zahl der Referenzen ist dann um weitere 2 zu hoch.
Allem Anschein nach hat dies aber nichts damit zu tun, welches Form
(bei mehreren Forms) in einem Projekt das gerade aktive Form ist.
Und es hat wohl auch nichts damit zu tun, ob ein Form geladen ist
oder nicht. Selbst wenn der Aufruf von GetRefCount indirekt erfolgt,
also etwa über eine Hilfsfunktion in einem Standard-Modul, ändert
dies nichts an diesem seltsamen Zuwachs. Erfolgt der Aufruf jedoch
direkt von einem anderen Modul aus, etwa aus irgend einem Ereignis
in einem anderen Form-Modul, stimmt der Referenz-Zähler wieder.
Im Zuge der bisher leider fruchtlosen Versuche, für dieses
Verhalten eine Erklärung zu finden, habe ich aber eine andere
interessante Entdeckung gemacht. Der Referenz-Zähler eines Forms
ist nämlich um 1 höher, wenn es geladen ist. Zur
zuverlässigen Ermittlung des Ladezustands eines Forms kann dieser
Unterschied im Referenz-Zähler jedoch nur dann dienen, wenn Sie
sicher sein können, dass keine weiteren Referenzen existieren, oder
aber wenn Sie deren Zahl genau kennen.
Der Referenz-Zähler ist auch ein Indiz für die Natur der
globalen Instanzen der Forms eines Projekts, die Sie ja direkt über
den (Objekt-)Namen eines Forms aufrufen und laden
können, ohne sie ausdrücklich instanzieren zu müssen. Selbst wenn
Sie die globale Instanz eines Forms entladen und gleich Nothing
setzen, werden Sie beim Aufruf von GetRefCount hierfür den Wert
1 erhalten. Ähnlich wie bei "As New ..."
deklarierten Objekten legt nämlich Visual Basic automatisch eine
Instanz im Hintergrund an (wenn auch nur gewissermaßen vorbereitend
und noch nicht voll initialisiert), sobald Sie darauf zugreifen.
Die Referenz-Zahl von Steuerelementen können Sie übrigens mit
GetRefCount nicht ermitteln, zumindest nicht für VB-eigene
Steuerelemente. Sie erhalten kein sinnvolles Ergebnis, sondern immer
eine gleiche, feststehende Zahl. Bei nicht VB-eigenen
Steuerelementen können Sie allerdings den Referenz-Zähler des
eigentlichen, über die Object-Eigenschaft erreichbaren
Steuerelement-Objekts ermitteln. Allerdings ist der Wert auch hier
wieder um 2 zu hoch, wenn Sie GetRefCount aus dem Form
heraus aufrufen, auf dem das Steuerelement platziert ist.
|