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 IWeakReference
interface:
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
Here's the WeakReference
class:
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:
Class: TheParent
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
And the TheChild
class:
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 TheParent
's Class_Terminate
handler, it's also hit - whereas if you replace the IWeakReference
with TheParent
in TheChild
, none of the two Class_Terminate
handlers run.
LenB
is to VBA whatsizeof
is to C. Use it to size the allocation for the copy memory API. \$\endgroup\$