缺少 VBA 错误方法名称的编译器消息

Missing VBA compiler message for wrong method name

考虑以下代码:

Public Sub VBACompilerIsMad()

    Dim Ap As Application     
    Dim Wb As Workbook
    Dim Ws As Worksheet

    Debug.Print Ap.XXX ' No compile error
    Debug.Print Wb.XXX ' No compile error
    Debug.Print Ws.XXX ' Compile error

End Sub

当我编译它时,我得到一个编译器错误,因为我引用了 Worksheet 的一个不存在的成员。但是,如果我注释掉最后一行,则不会出现编译器错误,即使 ApplicationWorkbook 都没有方法或 属性 XXX。就好像我将 ApWb 声明为 Object 变量。

为什么编译器对待 Application / WorkbookWorksheet 不同?

是否还有其他类似的 类,编译器似乎将它们视为 Object

一点假设:

您可以在 ADODB.Connection 对象 like a native method 上调用存储过程(在底部)。 (几个 msdn 站点上的示例看起来很奇怪)。
所以在VBS/VBA中有一些类似'anonymous/dynamic methods'的机制。 它可能是为 ApplicationWorkbook 类 在此处激活的类似机制 - 尽管我不知道确切的位置和方式。

一个测试支持基本思路:
我已经参考 Microsoft ActiveX Data Objects 2.8 Library:

对此进行了测试
Public Sub testCompiler()
    Dim cn As ADODB.Connection
    Dim cmd As ADODB.Command

    Debug.Print cn.XXX
    Debug.Print cmd.XXX
End Sub

cn.XXX 不会 抛出编译错误,cmd.XXX 会。

正如我所解释的那样(分别称赞),这是一个 COM 功能。

默认情况下,COM 假定接口是可扩展的,也就是说,它允许在 运行 时添加成员。如果这不是所需的行为,可以将 [nonextensible] attribute 应用于接口定义,它声明接口只接受在类型库中显式定义的方法。

dispinterface _Applicationdispinterface _Workbook 没有在 Excel 类型库中设置此标志,dispinterface _Worksheet 有。

,ADO 的dispinterface _Connection 没有[nonextensible]dispinterface _Command

要了解哪些是可扩展的,请在项目的参考和 运行:

中添加对 TypeLib Info 的引用
Dim t As tli.TLIApplication
Set t = New tli.TLIApplication

Dim ti As tli.TypeLibInfo
Set ti = t.TypeLibInfoFromFile("excel.exe")

Dim i As tli.InterfaceInfo
For Each i In ti.Interfaces
    If (i.AttributeMask And tli.TYPEFLAG_FNONEXTENSIBLE) <> tli.TYPEFLAG_FNONEXTENSIBLE Then
      Debug.Print i.Name
  End If
Next

你会看到这里几乎所有的接口都是可扩展的,所以大部分都被推出调试window,你只会看到最后一个。将 <> 改为 = 以打印那些不可扩展的,它们的数量要少得多。

GSerg 的回答确实很出色,我喜欢整个 COM 类型库 IDL 以及其中的一些属性如何控制 Excel VBA IDE 中的行为。愿 COM 的神秘知识得以传承!而且,我意识到这个问题已被悬赏以给予该答案更多的代表,但是当设置悬赏时它会出现在我的雷达上并且我对此事有看法。

因此,尽管 GSerg 的回答给出了机制,但它没有给出基本原理,即它给出了方法而不是原因。我会尝试回答原因。

Martin Roller (OP) 在他对 ApplicationWorksheetFunction 的评论中已经给出了一些答案。对我来说,这是保持 Application 可扩展性的一个令人信服的理由,我不会进一步考虑 Application

让我们转向 WorkbookWorksheet,我们最好从一些代码开始演示,因此您需要从两个新的工作簿开始,将它们命名为 MyWorkbook.xlsmOtherWorkbook.xlsm。所以一些说明:

OtherWorkbook.xlsm中转到代码模块ThisWorkbook并粘贴代码

Option Explicit

Public Function SomeFunctionExportedOffOtherWorkbook() As String
    SomeFunctionExportedOffOtherWorkbook = "Hello Matt's Mug!"
End Function

MyWorkbook.xlsm 中转到 Sheet1 代码模块并粘贴代码

Option Explicit

Public Function SomeFunctionExportedOffCodeBehindSheet1() As String
    SomeFunctionExportedOffCodeBehindSheet1 = "Hello Martin Roller!"
End Function

现在,在 VBA IDE 中将 Sheet1 的代号更改为 codebehindSheet1 现在,在 MyWorkbook.xlsm 中的新标准模块中添加以下代码

Sub TestingObjectLikeInterfacesOfWorkbookAndCodeBehindWorksheet_RunMany()

    '* For this example please rename the 'CodeName' for Sheet1 to be "codebehindSheet1" using the IDE
    Debug.Assert ThisWorkbook.Worksheets.Item("Sheet1").CodeName = "codebehindSheet1"


    Dim wb As Workbook
    Set wb = Application.Workbooks.Item("OtherWorkbook")

    '* Workbook dispinterface needs to not marked with nonextensible attribute
    '* so that it doesn't trip up over exported function in another workbook
    '* below SomeFunctionExportedOffOtherWorkbook is defined in the ThisWorkbook module of the workbook "OtherWorkbook.xlsm"
    Debug.Print wb.SomeFunctionExportedOffOtherWorkbook


    '*Not allowed --> Dim foo As Sheet1
    '*have to call by the 'code behind' name which is usually Sheet1 but which we changed to illustrate the point
    Debug.Print codebehindSheet1.SomeFunctionExportedOffCodeBehindSheet1


End Sub

现在运行上面这段代码。

您可能已经阅读了代码并希望理解我要表达的意思,但让我把它拼写出来。我们需要 Workbook 保持可扩展性,因为它可能包含对另一个工作簿的引用,该工作簿可能正在导出方法或函数,我们希望没有编译错误。

但是,对于 Worksheet,为了进行类似的导出,我们再次将代码添加到代码隐藏模块中,但是引用模块的方式有所不同:通过使用获取对该代码隐藏模块的引用它的 VBA 代号,大多数人不会从 Sheet1 更改它(这就是上面邀请您更改它的原因)。

所以模块名后面的代码获取的接口需要可扩展,而不是Excel.Worksheet接口。

P.S。谁有 TLI.dll 的副本?

作为解决方法,您仍然可以创建自己的 interface 并实现此接口。然后将变量声明为 INewInterface,所有编译器消息都将在那里 :)。这里是一个简单的示例,带有 UserForm 的自定义界面。 HTH

Interface

Public CancelButton As MSForms.CommandButton
Public DataList As MSForms.ListBox
Public CommandBox As MSForms.TextBox

Implementation

Implements IMyForm

Private Property Set IMyForm_CancelButton(ByVal RHS As MSForms.ICommandButton)

End Property

Private Property Get IMyForm_CancelButton() As MSForms.ICommandButton

End Property

Private Property Set IMyForm_CommandBox(ByVal RHS As MSForms.IMdcText)

End Property

Private Property Get IMyForm_CommandBox() As MSForms.IMdcText

End Property

Private Property Set IMyForm_DataList(ByVal RHS As MSForms.IMdcList)

End Property

Private Property Get IMyForm_DataList() As MSForms.IMdcList

End Property

Usage

注意:MyForm是已存在的VBA表格,已添加到项目中。