Search code examples
excelvbainterfacereference

Can I cast to any object's default interface?


I understand that in VBA, classes all expose a default interface (which is just the name of the class module). You can also make them Implement another custom interface; give the class some properties which are visible from the point of view of the custom interface, but not from the default interface.

I have a method which expects classes that Implement a certain interface

Public Sub doStuff(ByVal item As ICustomInterface)

Called like

Dim a As New Class1 'Implements ICustomInterface
Dim b As New Class2 'Implements ICustomInterface too

doStuff a
doStuff b
doStuff New Collection 'raises "runtime error 13 - type mismatch" as Collection doesn't implement ICustomInterface

If I understand correctly, when I provide an instance of an object to this method, by passing a reference to that object's default interface, VBA queries the object instance to generate a reference to the ICustomInterface of that object, and stores the new reference inside the item variable. This process I think is called downcasting.

My issue is that doStuff calls a method which requires passing the default interface of item, not the custom interface.


To demonstrate, we can use ObjPtr to identify which interface is being pointed to:

Dim implementation As Object
Dim defaultCast As Class1 'implements ICustomInterface
Dim downCast As ICustomInterface

Set implementation = New Class1 'or Class2 - store reference to default interface in variable

'1) Check if implementation indeed points to default interface
Set defaultCast = implementation
Debug.Assert ObjPtr(defaultCast) = ObjPtr(implementation) 'fine

'2) Check if down-casting gives different interface
Set downCast = implementation
Debug.Assert ObjPtr(downCast) <> ObjPtr(implementation) 'fine

'4) Check if casting from ICustomInterface to Object reverts to default interface
Dim objectUpCast As Object
Set objectUpCast = downCast
Debug.Assert ObjPtr(objectUpCast) = ObjPtr(implementation) 'fails :(
Debug.Assert ObjPtr(objectUpCast) = ObjPtr(downCast) 'succeeds - not what I want

'3) Check if casting from ICustomInterface to Class1 reverts to Class1's default interface
Dim strictUpCast As Class1
Set strictUpCast = downCast
Debug.Assert ObjPtr(strictUpCast) = ObjPtr(implementation) 'fine - but won't work if I Set implementation = New Class2
'/some other implementation of the ICustomInterface

The third option; taking a custom interface and reverting back to the default interface is what I want

Why?

I want type safety in my function signature. I could work explicitly with the custom interface - this is my current work-around

Public Sub doStuff(ByVal item As Object) 'receive default interface - or at least whatever interface is provided

    Dim downCast As ICustomInterface
    Set downCast = item

    'work with downCast as necessary
    '... later pass default interface "item" to other sub

End Sub

But I prefer the workflow of checking the type in my function signature and then up-casting back to default interface when I need to


Solution

  • Just cast to IUnknown first:

        Dim objectUpCast As Object
        Dim iUnk As IUnknown
        Set iUnk = downCast
        Set objectUpCast = iUnk
    

    Edit

    All VB intefaces are dual interfaces meaning all interfaces are derived from IDispatch which in turn is derived from IUnknown.

    Note that the Object datatype stands for the IDispatch interface.

    In your example:

    Dim strictUpCast As Class1
    Set strictUpCast = downCast
    

    the second line calls QueryInterface asking for the Class1 interface, as expected.

    In your other example:

    Dim objectUpCast As Object
    Set objectUpCast = downCast
    

    on the second line 2 things can happen:

    1. if the variable (downCast) points to a dual interface (an interface derived from IDispatch) then VB just increases the reference count (thus returning same interface as downCast)
    2. if the variable points to a custom interface (derived from IUnknown) then VB calls QueryInterface and asks for the IDispatch interface

    When casting to the IUnknown interface (part of the OLE Automation library):

    Dim iUnk As IUnknown
    Set iUnk = downCast
    

    the iUnk variable points to a custom interface (derived only from IUnknown).

    This gets to:

    Dim objectUpCast As Object
    Set objectUpCast = iUnk
    

    where, on the second line, VB calls QueryInterface and asks for the IDispatch interface because the variable (right side) points to a custom interface (IUnknown itself which does not derive from IDispatch). The IDispatch interface knows about the default interface and that is what is returned

    Edit 08-May-2024

    Assuming the Ole Automation reference is not on, then obviously IUnknown would raise a compile-time error.

    The alternative is to use any other custom interface. For example, VBEGlobal is always available as part of the VBA library itself:

    Public Function GetDefaultInterface(ByVal obj As Object) As Object
        Dim g As VBEGlobal
        MemLongPtr(VarPtr(g)) = ObjPtr(obj)
        Set GetDefaultInterface = g
        MemLongPtr(VarPtr(g)) = NULL_PTR
    End Function
    

    where MemLongPtr and NULL_PTR are part of LibMemory

    In the above function, a QueryInterface call is made for IDispatch in the same way as in the previous examples.