我知道在VBA中,类都公开一个默认接口(这只是类模块的名称)。您还可以让它们
Implement
成为另一个自定义界面;为类提供一些从自定义界面的角度可见的属性,但从默认界面的角度看不到。
我有一个方法需要实现某个接口的类
Public Sub doStuff(ByVal item As ICustomInterface)
称为喜欢
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
如果我理解正确的话,当我向此方法提供对象的实例时,通过传递对该对象的默认接口的引用,VBA 查询该对象实例以生成对该对象的
ICustomInterface
的引用,并存储新的item
变量内的引用。我认为这个过程称为向下转型。
我的问题是
doStuff
调用一个方法,需要传递项目的默认接口,而不是自定义接口。
为了演示,我们可以使用
ObjPtr
来识别所指向的接口:
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
第三种选择;采用自定义界面并恢复为默认界面是我想要的
我希望函数签名具有类型安全性。我可以明确地使用自定义界面 - 这是我当前的解决方法
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
但我更喜欢检查函数签名中的类型,然后在需要时向上转换回默认接口的工作流程
先投射到 IUnknown:
Dim objectUpCast As Object
Dim iUnk As IUnknown
Set iUnk = downCast
Set objectUpCast = iUnk
编辑
所有 VB 接口都是双接口,这意味着所有接口均派生自 IDispatch,而 IDispatch 又派生自 IUnknown。
请注意,Object 数据类型代表 IDispatch 接口。
在你的例子中:
Dim strictUpCast As Class1
Set strictUpCast = downCast
第二行调用 QueryInterface 来请求 Class1 接口,正如预期的那样。
在你的另一个例子中:
Dim objectUpCast As Object
Set objectUpCast = downCast
在第二行,可能会发生 2 件事:
当转换到 IUnknown 接口(OLE 自动化库的一部分)时:
Dim iUnk As IUnknown
Set iUnk = downCast
iUnk 变量指向自定义接口(仅从 IUnknown 派生)。
这意味着:
Dim objectUpCast As Object
Set objectUpCast = iUnk
在第二行,VB 调用 QueryInterface 并请求 IDispatch 接口,因为变量(右侧)指向自定义接口(IUnknown 本身不是从 IDispatch 派生的)。 IDispatch 接口知道默认接口,这就是返回的内容
编辑2024年5月8日
假设
Ole Automation
引用未打开,那么显然 IUnknown
会引发编译时错误。
替代方法是使用任何其他自定义界面。例如,
VBEGlobal
始终作为 VBA
库本身的一部分可用:
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
其中
MemLongPtr
和 NULL_PTR
是 [LibMemory] 的一部分https://github.com/cristianbuse/VBA-MemoryTools)
在上面的函数中,以与前面示例相同的方式对
QueryInterface
进行 IDispatch
调用。