In VB.NET gibt es eine schöne neue Einrichtung namens "WeakReference". Hiermit kann eine Referenz auf ein Objekt gespeichert werden, ohne dass dadurch die Zerstörung des Objektes verhindert wird. Dieser Artikel soll zeigen, wie "weiche Referenzen" in VB5/6 implementiert werden können. Wozu soll das gut sein? Problem: Zirkuläre Verweise Es gebe zwei Klassen Abteilung und Mitarbeiter. Jedes Abteilung-Objekt hat eine Aufzählung Mitarbeiter, in der alle Mitarbeiter-Objekte verzeichnet sind, die zu der Abteilung gehören. Umgekehrt hat jedes Mitarbeiter-Objekt eineAbteilung-Eigenschaft, die auf das Abteilung-Objekt verweist, das die Abteilung des Mitarbeiters repräsentiert. Dies ist eine klassische 1:N-Beziehung, zu jeweils einer Abteilung gibt es N Mitarbeiter.
Eine Implementierung in VB5/6 könnte so aussehen: Abteilung.cls Option Explicit Public Name As String Public Mitarbeiter As New Collection Public Sub AddMitarbeiter(ByVal strName As String) Dim m As New Mitarbeiter m.Name = strName Set m.Abteilung = Me Mitarbeiter.Add m End Sub Private Sub Class_Terminate() Debug.Print "Terminate Abteilung "; Me.Name End Sub Public Sub DebugPrint() On Error Resume Next Dim m As Mitarbeiter Debug.Print "Abteilung "; Me.Name For Each m In Mitarbeiter m.DebugPrint Next End Sub Mitarbeiter.cls Option Explicit Public Name As String Public Abteilung As Abteilung Private Sub Class_Terminate() Debug.Print "Terminate Mitarbeiter "; Me.Name End Sub Public Sub DebugPrint() On Error Resume Next Debug.Print "Abteilung "; Me.Abteilung.Name, "Mitarbeiter "; Me.Name End Sub Jetzt kann man in einem VB-Programm Abteilungen und Mitarbeiter hinzufügen: MainMod.bas Option Explicit Sub Main() Dim Einkauf As New Abteilung Dim Verkauf As New Abteilung With Einkauf .Name = "Einkauf" .AddMitarbeiter "Meier" .AddMitarbeiter "Schulze" End With With Verkauf .Name = "Verkauf" .AddMitarbeiter "Müller" .AddMitarbeiter "Schmidt" End With Einkauf.DebugPrint Verkauf.DebugPrint Set Einkauf = Nothing Set Verkauf = Nothing Stop End Sub Blendet man jetzt das Direktfenster ein und startet das Programm (Ctrl+G / F5), so wird das Programm durchgeführt und bei der Stop-Anweisung angehalten, ohne dass im Direktfenster bereits Terminate-Ereignisse protokolliert wären: Abteilung Einkauf Abteilung Einkauf Mitarbeiter Meier Abteilung Einkauf Mitarbeiter Schulze Abteilung Verkauf Abteilung Verkauf Mitarbeiter Müller Abteilung Verkauf Mitarbeiter Schmidt Erst wenn man das Programm mit F5 fortsetzt, werden die Objekte beendet: Terminate Abteilung Verkauf Terminate Abteilung Einkauf Terminate Mitarbeiter Schmidt Terminate Mitarbeiter Müller Terminate Mitarbeiter Schulze Terminate Mitarbeiter Meier Was hat das zu bedeuten? Obwohl in unserer Routine Main keine Referenzen mehr auf die Abteilung-Klasse bestehen, werden die entsprechenden Objekte offenbar noch nicht freigegeben, d.h. sie sind weiterhin im Hauptspeicher vorhanden. Grund ist, dass das Abteilung-Objekt nicht beendet werden kann, weil es noch Verweise darauf in den Mitarbeiter-Objekten gibt. Näheres findet sich in der VB-Dokumentation (MSDN) unter "Dealing with Circular References" bzw. "Umgang mit Zirkelbezügen". Es gibt Programme, bei denen dieses Verhalten nicht weiter stört. In anderen Fällen kann es zu Problemen wie Speicherfraß mit anschließendem Programmabsturz oder dazu führen, dass das Programm unfähig wird, sich selbst zu beenden. Lösung: Weiche Referenzen Eine schöne Lösung für dieses Problem bieten "weiche Referenzen". So gibt es in .NET eine WeakReference Klasse, mit deren Hilfe man Referenzen auf Objekte halten kann, ohne dass dies die Terminierung der referenzierten Objekte behindert. Die Kehrseite der Medaille: Es ist nicht gewährleistet, dass zu der Referenz auch noch ein Objekt besteht, wenn man sie benutzen will. So schön diese neue .NET-Welt auch ist, für VB5/6 braucht man eine eigene Lösung. Im neuen Modul WeakReference werden die Routinen WrefFromAbteilung, AbteilungFromWref und ReleaseAbteilung definiert, die weiche Referenzen auf Abteilung-Objekte ermöglichen: WeakReference.bas Option Explicit Private Declare Sub LongFromObject Lib "kernel32" _ Alias "RtlMoveMemory" ( _ dest As Long, _ src As Object, _ ByVal nbytes As Long) Private Declare Sub ObjectFromLong Lib "kernel32" _ Alias "RtlMoveMemory" ( _ dest As Object, _ src As Long, _ ByVal nbytes As Long) Private Declare Sub CopyMemory Lib "kernel32" _ Alias "RtlMoveMemory" ( _ dest As Any, _ src As Any, _ ByVal nbytes As Long) Private mWrefAbteilungen As Collection ' Hier sammeln wir alle weichen Referenzen. Wenn ein Objekt ' terminiert, wird seine Referenz hier entfernt. So kann ' überprüft werden, ob es zu einer extern gespeicherten Wref ' noch ein existierendes Objekt gibt. ' Public Function WrefFromAbteilung(ByVal a As Abteilung) As Long ' Liefert eine weiche Referenz auf ein Abteilung-Objekt On Error Resume Next Dim wref1 As Long Dim wref2 As Long If Not a Is Nothing Then Call LongFromObject(wref1, a, 4) ' Objektzeiger holen Err.Clear wref2 = mWrefAbteilungen(Hex(wref1)) ' Schon gespeichert? If Err Then ' Referenz noch nicht gespeichert Call mWrefAbteilungen.Add(wref1, Hex(wref1)) ' Referenz speichern End If WrefFromAbteilung = wref1 End If End Function Public Function AbteilungFromWref(ByVal wref As Long) As Abteilung ' Liefert das Objekt zur weichen Referenz, oder Nothing, ' wenn das Objekt nicht (mehr) existiert On Error Resume Next Dim a As Abteilung If wref <> 0 Then Call ObjectFromLong(a, wref, 4) ' Objekt holen IncRefCount a ' Referenzzähler erhöhen End If Set AbteilungFromWref = a End Function Public Sub ReleaseAbteilung(ByVal a As Abteilung) ' Baut weiche Referenzen zum Abteilung-Objekt ab On Error Resume Next Dim wref As Long If Not a Is Nothing Then Call LongFromObject(wref, a, 4) mWrefAbteilungen.Remove Hex(wref) End If End Sub Private Sub IncRefCount(obj As IUnknown) ' Erhöht den Referenzzähler (simuliert IUnkown.AddRef) If obj Is Nothing Then Exit Sub Dim lngRc As Long ' Referenzzählerwert holen... CopyMemory lngRc, ByVal (ObjPtr(obj)) + 4, 4 ' ...und fortschalten CopyMemory ByVal (ObjPtr(obj)) + 4, (lngRc + 1), 4 End Sub Jetzt können die "harten" Referenzen in der Klasse Mitarbeiter durch weiche Referenzen ersetzt werden: Mitarbeiter.cls Option Explicit Public Name As String ' Public Abteilung As Abteilung (ehemalige "harte" Referenz) Private mWrefAbteilung As Long Public Property Get Abteilung() As Abteilung Set Abteilung = AbteilungFromWref(mWrefAbteilung) End Property Public Property Set Abteilung(ByVal vNewValue As Abteilung) mWrefAbteilung = WrefFromAbteilung(vNewValue) End Property Private Sub Class_Terminate() Debug.Print "Terminate Mitarbeiter "; Me.Name End Sub Public Sub DebugPrint() On Error Resume Next Debug.Print "Abteilung "; Me.Abteilung.Name, "Mitarbeiter "; Me.Name End Sub In der Klasse Abteilung wird jetzt in der Ereignisprozedur Class_Terminate die Routine ReleaseAbteilung aufgerufen: Abteilung.cls ... Private Sub Class_Terminate() Debug.Print "Terminate Abteilung "; Me.Name ReleaseAbteilung Me End Sub ... Als Lohn dieser Mühe wird uns im Direktfenster bestätigt, dass jetzt alle Objekte schon an der Stop-Marke korrekt abgeräumt wurden: Abteilung Einkauf Abteilung Einkauf Mitarbeiter Meier Abteilung Einkauf Mitarbeiter Schulze Abteilung Verkauf Abteilung Verkauf Mitarbeiter Müller Abteilung Verkauf Mitarbeiter Schmidt Terminate Abteilung Einkauf Terminate Mitarbeiter Meier Terminate Mitarbeiter Schulze Terminate Abteilung Verkauf Terminate Mitarbeiter Müller Terminate Mitarbeiter Schmidt Hintergrund: COM-Referenzzähler Wozu dient die Routine IncRefCount? Nun, wie der Kommentar schon sagt, wird damit der Referenzzähler des als Argument übergebenen Objekts erhöht. Was aber hat es damit auf sich? Alle Objekte in VB-Classic sind COM-Objekte - und die haben die angenehme Eigenschaft, dass sie ihre Entsorgung selbst erledigen. Dies ist überhaupt der Schlüssel zum Erfolg von Visual Basic. Wer sich schon mal in Allokation und Freigabe von Speicherbereichen z.B. in C oder C++ geübt hat, weiß das zu schätzen. COM hat also für jedes Objekt einen Referenzzähler. Sobald eine neue Referenz auf ein Objekt erzeugt wird (in COM: IUnknown.AddRef), wird dieser Zähler erhöht, wenn eine Referenz abgebaut wird (COM: IUnknown.Release), wird der Zähler erniedrigt. Ist der Zähler bei 0 angekommen, so entsorgt IUnknown.Release das Objekt, weil es dann ja von niemandem mehr verwendet wird. In VB wird eine Referenz bei der Zuweisung eines Objekts zu einer Variablen erzeugt. Die Referenz wird in VB abgebaut, wenn der Variablen ein anderer Wert (z.B. Nothing) zugewiesen wird. Somit dürfte auch klar werden, wie wir hier "weiche Referenzen" implementiert haben: Es sind einfach Referenzen, die nicht im Referenzzähler gezählt werden. Im ersten Beispiel (mit den "harten" Referenzen) hat sich der Referenzzähler des Abteilung-Objektes erhöht, wenn wir der Abteilung-Variablen im Mitarbeiter-Objekt ein Abteilung-Objekt zugewiesen haben. Im zweiten Beispiel (mit den "weichen" Referenzen) wurde aus dem Abteilung-Objekt nur ein Long-Wert (die Adresse des Objektes) erzeugt und abgespeichert. Da wir kein Objekt (im VB-Sinn) gespeichert haben, wurde auch der Referenzzähler nicht erhöht. Wenn wir aber (in der Routine AbteilungFromWref) aus der "weichen Referenz" wieder ein Objekt gemacht haben, musste der Referenzzähler des COM-Objektes (mit der Routine IncRefCount) erhöht werden. Es lohnt sich, bei beiden Beispielen einmal den Referenzzähler im Einzelschrittmodus des VB-Debuggers zu beobachten. Dazu fügen wir zunächst folgende Routine hinzu: WeakReference.bas ... Public Function RefCount(obj As IUnknown) As Long ' liefert den Referenzzähler von obj If obj Is Nothing Then Exit Function Dim lngRc As Long ' Referenzzählerwert holen... CopyMemory lngRc, ByVal (ObjPtr(obj)) + 4, 4 RefCount = lngRc - 2 End Function Jetzt wechseln wir zur Routine Main, machen einen Rechtmausklick und wählen "Überwachung hinzufügen...". Unter "Ausdruck" geben wir RefCount(Einkauf) ein und bestätigen mit OK. Jetzt gehen wir mit F8 im Einzelschrittmodus Schritt für Schritt durch den Code und beobachten dabei den Referenzzähler im Überwachungsfenster. Zum Abschluss noch eine Preisfrage:
1. (und einziger) Preis ist eine vertiefte Erkenntnis in das Problem der zirkulären Referenzen unter VB.
Dieser Workshop wurde bereits 20.433 mal aufgerufen.
Anzeige
![]() ![]() ![]() Ein absolutes Muss - Geballtes Wissen aus mehr als 8 Jahren vb@rchiv! - nahezu alle Tipps & Tricks und Workshops mit Beispielprojekten - Symbol-Galerie mit mehr als 3.200 Icons im modernen Look Weitere Infos - 4 Entwickler-Vollversionen (u.a. sevFTP für .NET), Online-Update-Funktion u.v.m. |
TOP Entwickler-Paket ![]() TOP-Preis!! Mit der Developer CD erhalten Sie insgesamt 24 Entwickler- komponenten und Windows-DLLs. Die Einzelkomponenten haben einen Gesamtwert von 1605.50 EUR... Tipp des Monats ![]() Dieter Otter PopUp-Menü wird nicht angezeigt :-( In diesem Tipp verraten wir Ihnen, wie Sie Probleme mit PopUp-Menüs umgehen können, wenn diese unter bestimmten Umständen einfach nicht angezeigt werden. TOP! Unser Nr. 1 ![]() Neu! sevDataGrid 3.0 Mehrspaltige Listen, mit oder ohne DB-Anbindung. Autom. Sortierung, Editieren von Spalteninhalten oder das interaktive Hinzufügen von Datenzeilen sind ebenso möglich wie das Erstellen eines Web-Reports. |
|||||||||||||||
Microsoft, Windows und Visual Basic sind entweder eingetragene Marken oder Marken der Microsoft Corporation in den USA und/oder anderen Ländern. Weitere auf dieser Homepage aufgeführten Produkt- und Firmennamen können geschützte Marken ihrer jeweiligen Inhaber sein. |