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

When are two objects the same in VBA?

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

<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="rx_onLoad">
    <ribbon>
        <tabs>
            <tab id="TestTab" label="Test Tab" insertAfterMso="TabHome">
                <group id="TestGroup" label="TestGroup">
                    <button id="TestButton" label="TestButton" size="normal" onAction="OnTestButton"  tag="TestButton" imageMso="Coffee" /> 
                </group>        
            </tab>
        </tabs>
    </ribbon>
</customUI>

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

主动sheet有方法

Public Sub Test()
    MsgBox "Test"
End Sub

如果我单击测试按钮,将调用方法 OnTestButton。 对象 Control.Context.ActiveSheetActiveSheetIS 运算符下是相同的。当我在 WorkSheet 的界面中使用像 Name 这样的 属性 时,它们的行为相同。 但是,当我调用不在接口中的方法 Test 时,我在 Control.Context.ActiveSheet 上收到运行时错误 458 "Object does not support this property or method" 但在 ActiveSheet.

上没有

那么,为什么两个引用 Control.Context.ActiveSheetActiveSheet 在运行时表现不同,而它们应该引用 "same" 对象?

我有理由 (>90%) 确定以下内容是正确的。

先回答题目中的问题:VBA什么时候两个对象相同?

两个对象在VBA中是相同的,当COM说它们相同时,COM说它们是相同的,当你request the IUnknown interface from both and the pointers come out equal.

现在到有问题的对象。

丝带的Control.Context无非就是Excel的Application.ActiveWindow,所以问题就变成了,ActiveWindow.ActiveSheetApplication.ActiveSheet 一样。

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


旁注。
Excel 对于 "true object".[=] 的单个 "actual" "internal" 实例具有多个 "external" "instances" 对象是很自然的74=]

例如您可以创建 Range 对象的多个实例,所有这些实例都是不同的实例 (Is = False),它们引用实际 sheet 上完全相同的实际范围。因此,对于 "actual thing".

,每个人都只是 "viewport"

我推测 Windows 和 Sheets 正在发生类似的事情(每个实际事物可能有多个 "viewports"),但是 Excel开发人员,为了避免 confusion/to 简化 VBA 编码,决定让 Sheets 包装器通过 returning 相同的 IUnknown 来报告它们是同一对象指针。

而对于COM而言,这很好:只要遵循所有COM规则,对象内部实现为多个对象并不重要,因为只要遵循所有COM规则,就有无论如何都无法区分它们。


现在真正的问题是,为什么不能在 ActiveWindow.ActiveSheet.

上调用 Test()

因为ActiveWindow.ActiveSheet return是Worksheet接口,它没有Test()方法,是。就这么简单。

那为什么Test()可以在Application.ActiveSheet上调用呢?
因为 Application.ActiveSheet 没有 return 一个 Worksheet。它 returns Sheet1(或任何你的 sheet 被命名)。

Sheet1 是一个动态接口,它继承自 Worksheet 并包含您的 Test()。它是 Worksheet.

的超集

您可能想知道,当用户尝试在 Worksheet 上调用 Test() 时,为什么 VBA 不请求更好的 Worksheet 超集。答案是,不应该,也不可以!

VBA 不应该也不需要了解托管它的应用程序的内部实现细节。它无法知道存在一个"better"接口可以从它当前拥有的接口查询。如果它确实怀疑有一个 "better" 接口,它究竟会尝试查询哪个接口,因为每个对象都可以有数百个接口?

VBA 唯一需要执行后期绑定调用的是 IDispatch 接口。
所有 Excel 接口都继承自 IDispatch。 即ExcelIs IDispatch.

中的每个class

As Object 类型的变量也表示 As IDispatch.
将某些内容设置为 Object 变量意味着从该对象中查询 IDispatch

ActiveWindow.ActiveSheet returns IDispatchWorksheet 的一部分。它是有效的,完整的 IDispatch 存储在 IDispatch 类型的变量中,因此 不需要 VBA 请求 "better IDispatch"。它使用已有的那个,调用失败。

Application.ActiveSheet returns IDispatchSheet1 的一部分,一个不同的界面。这次Test()成功了

Worksheet ActiveWindow.ActiveSheet returning IDispatch 是否是一个错误是有争议的。
从技术上讲,这不是错误,因为 Worksheet IDispatch 所以该方法是 return 的权利。
然而,可以说 return 对 "better IDispatch" 是我们在 Excel 中滚动的方式,他们真的应该这样做。
我个人倾向于宣布这是一个小错误。

不过你可以自己申请"better IDispatch"。
如果你简单地声明另一个 Object 变量并且 Set 你现有的工作 sheet 引用它是行不通的 - VBA 会观察到这两个变量属于同一类型, Object,并且会直接复制指针而不是尝试查询另一个接口。

要让 VBA 实际请求另一个接口,您需要先查询与 IDispatch 不同的另一个接口,然后从中请求 IDispatch

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