VB6 circular references: explicit Cleanup method or ObjPtr weak back-reference?
0 reputation · 06 May 2020, 15:31 UTC
0 reputation · 06 May 2020, 15:31 UTC
Classic Visual Basic 6 (pre-.NET) and VBA destroy a COM object only when its reference count reaches zero. A parent class and child class that hold references to each other therefore never terminate: Class_Terminate never fires, and any cleanup code placed there silently never runs.
Two documented ways to break such a cycle exist:
Cleanup method that sets the child's back-reference to Nothing; type-safe, but every call site must remember to invoke it.ObjPtr value (an undocumented VB6 internal) or a keyed lookup; structurally leak-free, but dereferencing a parent that already died risks a crash.The constraint: in a long-lived VB6 codebase, third-party call sites cannot all be audited, yet cleanup must still run. Teardown order — who nulls whose reference first — is left to the developer.
Cleanup?ObjPtr is undocumented, is it stable enough to rely on across VB6 runtime builds, or should it be treated as a last resort?Class_Terminate eventually fires?30775 reputation · 06 May 2020, 19:07 UTC
When third-party callers cannot be trusted to invoke a manual Cleanup method, the weak back-reference (ObjPtr) approach is the only way to ensure objects are destroyed without external intervention. Because VB6 relies on strict COM reference counting, an explicit teardown method is a policy, not a mechanism; if it is not called, the memory leaks. Using ObjPtr prevents the reference cycle from ever forming, allowing Class_Terminate to fire as soon as the primary external reference is released.
While ObjPtr is undocumented, it has remained stable across VB6 runtime builds because it simply retrieves the raw memory address of the COM object. However, it is a "dangerous" tool because it bypasses the reference counter. If the parent object is destroyed while the child still attempts to use the ObjPtr, the application will likely crash or throw an Object variable or With block variable not set error.
In a circular reference, the cycle is a closed loop. Breaking the link in either direction (Parent $\rightarrow$ Child or Child $\rightarrow$ Parent) is sufficient to allow the reference count to drop toward zero. It does not matter which side is nulled, provided that no other external references to those objects exist.
Long using ObjPtr(ParentObject).Set operation to cast the Long back into an object reference temporarily.Debug.Print "Terminated" statement to the Class_Terminate event of both classes to confirm they are being released.' In Child Class
Private m_ParentPtr As Long
Public Property Set Parent(ByVal pParent As ParentClass)
m_ParentPtr = ObjPtr(pParent)
End Property
Private Function GetParent() As ParentClass
' Warning: This assumes the parent still exists in memory
Set GetParent = ObjectFromPointer(m_ParentPtr)
End Function
Note: ObjectFromPointer is a common API-based wrapper used in VB6 to convert a Long back to an object.
To refine this recommendation: Are the Child objects owned exclusively by the Parent, or can they outlive the Parent? If Children can outlive the Parent, the ObjPtr approach requires a validation check to prevent crashes upon dereferencing.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.