VBA中的两个对象何时相同?

VBA中的两个对象何时相同?,vba,identity,Vba,Identity,我在Excel 2010中使用了一个功能区,其中包含一个按钮: OnTestButton方法在一个模块中实现 Sub OnTestButton(Control As IRibbonControl) Dim Ws As Object Set Ws = Control.Context.ActiveSheet MsgBox ActiveSheet Is Ws ' Shows True Debug.Print ActiveSheet.Name 'OK D

我在Excel 2010中使用了一个功能区,其中包含一个按钮:


OnTestButton方法在一个模块中实现

Sub OnTestButton(Control As IRibbonControl)

    Dim Ws As Object
    Set Ws = Control.Context.ActiveSheet
    MsgBox ActiveSheet Is Ws  ' Shows True

    Debug.Print ActiveSheet.Name 'OK
    Debug.Print Ws.Name ' OK

    ActiveSheet.Test ' OK
    Debug.Print Ws.Test ' Runtime Error

End Sub
活动工作表有一个方法

Public Sub Test()
    MsgBox "Test"
End Sub
如果单击测试按钮,则调用方法
OnTestButton
。 对象
Control.Context.ActiveSheet
ActiveSheet
IS
-操作符下是相同的。当我在
工作表
的界面中使用类似
名称
的属性时,它们的行为是相同的。 但是,当我调用不在接口中的方法
Test
时,我在
Control.Context.ActiveSheet
上得到运行时错误458“Object不支持此属性或方法”,但在
ActiveSheet
上没有

那么,为什么这两个引用
控制.Context.ActiveSheet
ActiveSheet
在运行时应该引用“相同”的对象时表现不同呢?

我有理由(>90%)确信下面的说法是正确的

首先回答标题中的问题:VBA中的两个对象何时相同

在VBA中,当COM表示两个对象相同时,这两个对象是相同的;而当您使用VBA时,COM表示两个对象是相同的

现在来谈谈这些问题

功能区的
控件.Context
只不过是Excel的
应用程序.ActiveWindow
,所以问题变成了,
ActiveWindow.ActiveSheet
还是
Application.ActiveSheet
一样。

是的,他们是——就COM而言。
它们可能不是作为单个对象在内部实现的,因为它们的指针彼此相距很远,但是当您向它们请求
IUnknown
时,它们返回相同的指针值。(您可以通过声明类型为
IUnknown
的变量来请求
IUnknown
,并
将对象设置为该类型。)


旁注。
对于Excel来说,对于一个“真实对象”的单个“实际”“内部”实例,有多个对象的“外部”“实例”是很自然的

例如,您可以创建
范围
对象的多个实例,所有这些实例都将是不同的实例(
Is=False
),引用实际工作表上完全相同的实际范围。因此,每一个都只是“真实事物”的“视口”

我推测类似的事情也发生在
Window
s和
Sheet
s上(每个实际的东西可以有多个“视口”),但是Excel开发人员为了避免混淆/简化VBA编码,决定让
Sheet
s包装器通过返回相同的
IUnknown
指针来报告它们是相同的对象

就COM而言,这很好:只要遵循所有COM规则,对象是否在内部实现为多个对象并不重要,因为只要遵循所有COM规则,就没有办法区分它们


现在实际的问题是,为什么不能在
ActiveWindow.ActiveSheet
上调用
Test()

因为
ActiveWindow.ActiveSheet
返回
工作表
接口,该接口没有
Test()
方法,并且是。就这么简单

那么为什么可以在
Application.ActiveSheet
上调用
Test()

因为
Application.ActiveSheet
不返回
工作表
。它返回
Sheet1
(或任何工作表名称)

Sheet1
是一个动态界面,它继承自
工作表
,并包含您的
Test()
。它是
工作表
的超集

您可能想知道,当用户试图在
工作表上调用
Test()
时,为什么VBA不请求更好的
工作表超集。答案是,它既不应该,也不能

VBA不应该也不需要了解托管它的应用程序的内部实现细节。它无法知道存在一个“更好”的接口,可以从当前的接口查询该接口。如果它确实怀疑有一个“更好”的接口,那么考虑到每个对象可以有数百个接口,它会尝试查询哪个接口

VBA执行后期绑定调用所需的唯一东西是
IDispatch
接口。
所有Excel界面都继承自
IDispatch
。 也就是说,Excel
中的每个类都是IDispatch

类型为
As Object
的变量也表示
As IDispatch

将某物设置为
对象
变量意味着从该对象中查询
IDispatch

ActiveWindow.ActiveSheet
返回
IDispatch
,它是
工作表的一部分。它是有效的、完整的
IDispatch
存储在
IDispatch
类型的变量中,因此VBA无需要求“更好的
IDispatch
。它使用它已有的一个,调用失败

Application.ActiveSheet
返回
IDispatch
,它是另一个界面
Sheet1
的一部分。这次
Test()
成功

工作表
返回
IDispatch
ActiveWindow.ActiveSheet
是否是一个bug还有争议。
从技术上讲,这不是一个bug,因为
工作表
IDispatch
,因此该方法有权返回该值。
但是可以说,返回“better
IDispatch
”是我们在Excel中滚动的方式,他们真的应该这样做。
我个人倾向于宣布它是一个小错误

但你可以要求
Dim BadIDispatch As Object
Set BadIDispatch = Control.Context.ActiveSheet  'ActiveWindow.ActiveSheet

Dim Ws As Worksheet
Set Ws = BadIDispatch  'Querying another interface

Dim WsAsObject As Object
Set WsAsObject = Ws    'Querying IDispatch - this time going to get a good one