Tuesday, 23 May 2017

COM Support Classes

Writing C++ code currently to enhance the VBA experience and so I'm dealing with Automation types very frequently. A good VBA ought to know that a VBA string is in fact a BSTR and that a VBA Variant also has a C++ counterpart.

So BSTRs have memory management rules which need to be observed otherwise one will leak memory. Thankfully C++ classes using RAII design patterns can assist. It just seems there are more than one to choose from and it is worth documenting them here on a blog entry.
BSTR helper classesVendorLibraryHeader
_bstr_t ClassMicrosoftNative C++ compiler COM supportcomutil.h
CComBSTR ClassMicrosoftATLatlbase.h


Variant also has helper classes
Variant helper classesVendorLibraryHeader
_variant_t ClassMicrosoftNative C++ compiler COM supportcomutil.h
CComVariant ClassMicrosoftATLatlbase.h


Also found a nice page of sample code demonstrating the Native C++ compiler COM support classes interacting with ADO objects which are normally scripted with VBA and thus have automation-typed interfaces. Visual C++ ADO Programming

Also Stack Overflow question discussing the classes available. Looks like the classes listed above represent the sum of Microsoft options.

Tuesday, 2 May 2017

DispCallFunc opens a new door to COM interfaces and VBA Function Pointers

So during my research on Type Information it seems that Dispatch carries with it some amazing type querying functionality. Not only that it seems IDispatch and Type Libraries interrogation interfaces are pretty much the same

I always knew IDispatch had to do some work but looking into it seems both amazing and fascinating. Also, it seems that there are some COM system functions that act as low level helpers, one such function is DispCallFunc found in oleaut32.dll.

A clever man called Krivous Anatolii Anatolevich has used this to allow VBA programmers call into IClassFactory::CreateInstance; a problem I solved only with resort to C++/VBA hybrid code. Lots of interesting stuff there at his GitHub page.

Another clever man Mikael Katajamäki has discovered how to use DispCallFunc to form part of a solution for a recursive mathematical root finder. His code is over at Windows API DispCallFunc as function pointer in VBA

Whilst VBA already has AddressOf and Application.OnTime, often it is required to pass parameters to a callback function. DispCallFunc may give the solution to that problem instance.

This idea is debated in an excellent thread discussion over at vbforums.com

As a footnote and coda and not related to DispCallFunc is an incredible project that writes assembly code into memory before calling it. This can be found at wonderful website called FreeVBcode.com to which I shall be returning. The said code is found at Call API functions by Name, without Declare, v 2.0

Solve VBA Potentially Massive Memory Leak

So I like object orientation, I also like object models like that of Microsoft Excel.  Excel's object model is very intuitive, you can see the application window, interact with workbooks and worksheets and the cells there.  Analogously, in code, you can navigate the object model from the Application object (normally implied) to the Workbooks collection, to a specific Workbook, then to the Worksheets collection, then to a specific Worksheet and finally a Cells object.  With the Cells object you can finally write code to write to the cell.

The Excel object model is wonderful to navigate, indeed the Excel object model is navigable in both directions as Cells object has a parent property as does the Worksheet, Worksheets, Workbook and Workbooks classes which allows navigation up the chain to the root Application object.

I like to emulate this sort of object model in my own VBA applications but there is a problem here; it is one of circular references and reference counting.  If not careful, this can lead to objects locked in memory and not being destructed.  Imagine a whole elegant rich hierarchy of objects such as that of Excel not being destructed, the memory leak would be massive.

If we have a two-way navigation then a Parent object will have a reference to a Child object and vice versa.  Upon termination of a Child object it is necessary to call a Dispose method on the Child to manually disconnect the Parent, i.e. Set the reference to Parent to Nothing.

(Incidentally this problem is one reason why .NET factored out the IDisposable interface)

Here is our first piece of code which is a Child class

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Child"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Private msName As String

Private moParent As Parent '<-- circular reference potential big problem!

Public Property Let Name(ByVal sName As String)
    msName = sName
End Property
Public Property Get Name() As String
    Name = msName
End Property

Public Sub SetParent(ByVal oParent As Parent)
    Set moParent = oParent
End Sub

Public Sub Dispose()
    '* before quitting remove reference to parent to break circular reference
    '* otherwise potential massive memory leak as Parent as Child have a
    '* circular reference where each other stops the other from being
    '* destructed
    Set moParent = Nothing
    '* but this must be called manually, so must be implemented in Parent
    '* class which also must be called manually.  But sometimes people forget!
End Sub

Private Sub Class_Terminate()
    Dispose
End Sub

So you can see the Parent reference being set in the Sub procedure SetParent(). You can also see the Parent reference being cleared in Dispose().

Now let us look at the Parent class

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "Parent"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit

Private mcolChildren As New VBA.Collection

Public Sub AddChild(ByVal oChild As Object)
    Call oChild.SetParent(Me)
    
    mcolChildren.Add oChild
End Sub

Public Sub Dispose()
    Dim lLoop As Long
    
    '* loop through in reverse to avoid moving the goal posts re index
    For lLoop = mcolChildren.Count To 1 Step -1
        
        '* need to call Dispose on Child to break any circular reference with Parent
        '* unless using weak references
        mcolChildren.Item(lLoop).Dispose
        
        mcolChildren.Remove lLoop
    Next lLoop
End Sub

Private Sub Class_Terminate()
    '* Sadly putting Dispose() in Class_Terminate won't save you
    '* Class_Terminate only runs when reference count falls to zero
    '* and getting the reference count to zero is what Dispose is meant to do!
    '* you can uncomment and try it if you're skeptical...
    'Dispose
    Debug.Print "Parent Destructor called"
End Sub


You can see the Parent has a Dispose method which should be called when the Parent object is to be destructed. Parent's implementation of Dispose loops through the collection (in reverse so index is well behaved) and calls Dispose on each Child before removing it from the collection. This is the correct and proper way to untangle the circular references BUT it relies upon the client code calling Dispose on the Parent before termination and sometimes programmers forget.

Let us now see some client code, in a standard module paste the following code

Sub TestChildWithDispose()
    
    Dim oParent As Parent
    Set oParent = New Parent

    oParent.AddChild CreateChild("Luke")
    oParent.AddChild CreateChild("Leia")
    
    '* comment out following line to see proof that the
    '* parent's destructor is not called in the case
    '* of circular references
   'oParent.Dispose
    
    Set oParent = Nothing

End Sub

Function CreateChild(ByVal sName As String) As Child
    Dim oChild As Child
    Set oChild = New Child
    oChild.Name = sName
    Set CreateChild = oChild
End Function

You can experiment with the above code by commenting and uncommenting the line that reads "oParent.Dispose" to demonstrate how the Parent Child objects are not destructed unless you call the Parent's Dispose method.

So always ship a Dispose method and make sure it gets called!!!