Reputation: 5543
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
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
Upvotes: 4
Views: 522
Reputation: 4578
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:
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.
Upvotes: 3