Sometimes in advanced OOP scenarios, a class needs to hold instances of another class which needs to hold a reference to the "parent". For example when you have a dynamic
UserForm control that needs to "call back" to the parent form that created it, or when you have a
ViewAdapter that talks to some UI, which in turn needs to "call back" to the adapter.
Such relationships create circular references, and if nothing is done to solve this, the objects don't get cleaned up and you're looking at what's essentially a memory leak.
With the help of Comintern I've written a class that solves this problem, and called it
WeakReference - in order to make the API as simple to use as possible, I wrapped it with an
VERSION 1.0 CLASS BEGIN MultiUse = -1 'True END Attribute VB_Name = "IWeakReference" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = False Attribute VB_PredeclaredId = False Attribute VB_Exposed = True Option Explicit Public Property Get Object() As Object End Property
VERSION 1.0 CLASS BEGIN MultiUse = -1 'True END Attribute VB_Name = "WeakReference" Attribute VB_GlobalNameSpace = False Attribute VB_Creatable = False Attribute VB_PredeclaredId = True Attribute VB_Exposed = False Option Explicit Implements IWeakReference #If Win64 Then Private Declare PtrSafe Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (hpvDest As Any, hpvSource As Any, ByVal cbCopy As LongPtr) #Else Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (hpvDest As Any, hpvSource As Any, ByVal cbCopy As Long) #End If Private Type TReference Address As Long End Type Private this As TReference Public Function Create(ByVal instance As Object) As IWeakReference With New WeakReference .Address = ObjPtr(instance) Set Create = .Self End With End Function Public Property Get Self() As IWeakReference Set Self = Me End Property Public Property Get Address() As Long Address = this.Address End Property Public Property Let Address(ByVal value As Long) this.Address = value End Property Private Property Get IWeakReference_Object() As Object ' Bruce McKinney's code for getting an Object from the object pointer: Dim objT As Object CopyMemory objT, this.Address, 4 Set IWeakReference_Object = objT CopyMemory objT, 0&, 4 End Property
Can this class be improved? Is the interface & factory method overkill?
Here's a simple example usage scenario:
Option Explicit Private child As TheChild Private Sub Class_Initialize() Set child = New TheChild Set child.Parent = Me End Sub Private Sub Class_Terminate() Set child = Nothing End Sub
Option Explicit Private ref As IWeakReference Public Property Get Parent() As TheParent Set Parent = ref.Object End Property Public Property Set Parent(ByVal value As TheParent) Set ref = WeakReference.Create(value) End Property Private Sub Class_Terminate() Stop ' expected break here when TheParent is terminated Set ref = Nothing End Sub
And a little procedure to test everything:
Public Sub Test() Dim p As TheParent Set p = New TheParent Debug.Print ObjPtr(p) Set p = Nothing End Sub
As expected, the
Stop statement is hit in
TheChild, and if you put a breakpoint in
Class_Terminate handler, it's also hit - whereas if you replace the
TheChild, none of the two
Class_Terminate handlers run.