Not reference counting
Unfortunately, Visual Basic doesn’t have a syntax for creating an uncounted reference. Any reference you create will be counted automatically. Fortunately, there’s a way to immediately decrement that reference as if it never happened. Let’s look at the code for how this is done in the FSearch form. We’ll do this with a very unusual Property Get/Set pair. First let’s look at the internal variable shared by the Get and Set procedures:
‘ Pointer for XEditor reference
Private pEditor As Long
Behind all the fancy talk, a COM object is nothing more than a pointer, and we’ll have to treat it as a pointer if we want to avoid the normal behavior that is so convenient in most situations but so much in the way for owned objects.
We talked about how the XEditor control creates and initializes an FSearch form in Chapter 9. The key point is that XEditor passes a reference to itself to FSearch with the following line:
Set finddlg.Editor = Me
This calls the Editor Property Set procedure on FSearch. Let’s see how it works:
Friend Property Set Editor(ByVal editorA As XEditor)
‘ Store an Editor pointer rather than an Editor object
‘ No AddRef for storing the pointer
pEditor = ObjPtr(editorA)
End Property
The undocumented ObjPtr function (introduced with its cousins StrPtr and VarPtr in Chapter 2) returns a dumb pointer rather than a smart object. Dumb is the idea here. The Property Set procedure will be called just once—when FSearch is created. It stores the pointer and every time FSearch needs to use the XEditor instance, it calls the Property Get. That’s where the real magic happens:
Friend Property Get Editor() As XEditor
Dim editorI As XEditor
‘ Turn editorI into an illegal, uncounted interface pointer
CopyMemory editorI, pEditor, 4
‘ Do NOT hit the End button here! You will crash!
‘ Assign to legal reference (VB AddRefs it)
Set Editor = editorI
‘ Still do NOT hit the End button! You will still crash!
‘ Destroy the illegal reference
CopyMemory editorI, 0&, 4
‘ OK, hit the End button if you must
‘ Internal XEditor reference goes out of scope (VB Releases it)
End Property
This looks dangerous—and it is. The whole point is to access XEditor without permanently incrementing its reference count. The local copy editorI will hold the unreferenced copy. The CopyMemory API procedure is used to copy the object pointer to the object, thus bypassing the normal object reference scheme. The editorI object will not have its reference count incremented. If you terminate your program early (by clicking End where I told you not to), Visual Basic will try to destroy the XEditor instance twice—once for the original reference and once for this uncounted reference. The first time it will call Release, which will decrement the reference and, finding a count of 0, destroy the object. The second time it will call Release on an object that no longer exists. Crash!
As long as you don’t try to release it, the illegal reference works fine. You can assign it to a legal reference, such as the return value of the Property Get. What you can’t do is let this illegal reference go out of scope. If you do, Visual Basic will release it, which will destroy the XEditor object, and the next time anyone tries to access XEditor from FSearch or anywhere else—crash! The purpose of the second CopyMemory is to set the illegal reference to Nothing (which happens to be a null pointer) without decreasing its reference count. Visual Basic won’t call Release on Nothing, and editorI won’t cause a crash when it goes out of scope.