缺少 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
的一个不存在的成员。但是,如果我注释掉最后一行,则不会出现编译器错误,即使 Application
和 Workbook
都没有方法或 属性 XXX
。就好像我将 Ap
和 Wb
声明为 Object
变量。
为什么编译器对待 Application
/ Workbook
与 Worksheet
不同?
是否还有其他类似的 类,编译器似乎将它们视为 Object
?
一点假设:
您可以在 ADODB.Connection 对象 like a native method 上调用存储过程(在底部)。
(几个 msdn 站点上的示例看起来很奇怪)。
所以在VBS/VBA中有一些类似'anonymous/dynamic methods'的机制。
它可能是为 Application
和 Workbook
类 在此处激活的类似机制 - 尽管我不知道确切的位置和方式。
一个测试支持基本思路:
我已经参考 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 _Application
和 dispinterface _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) 在他对 Application
和 WorksheetFunction
的评论中已经给出了一些答案。对我来说,这是保持 Application
可扩展性的一个令人信服的理由,我不会进一步考虑 Application
。
让我们转向 Workbook
和 Worksheet
,我们最好从一些代码开始演示,因此您需要从两个新的工作簿开始,将它们命名为 MyWorkbook.xlsm
和 OtherWorkbook.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表格,已添加到项目中。
考虑以下代码:
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
的一个不存在的成员。但是,如果我注释掉最后一行,则不会出现编译器错误,即使 Application
和 Workbook
都没有方法或 属性 XXX
。就好像我将 Ap
和 Wb
声明为 Object
变量。
为什么编译器对待 Application
/ Workbook
与 Worksheet
不同?
是否还有其他类似的 类,编译器似乎将它们视为 Object
?
一点假设:
您可以在 ADODB.Connection 对象 like a native method 上调用存储过程(在底部)。
(几个 msdn 站点上的示例看起来很奇怪)。
所以在VBS/VBA中有一些类似'anonymous/dynamic methods'的机制。
它可能是为 Application
和 Workbook
类 在此处激活的类似机制 - 尽管我不知道确切的位置和方式。
一个测试支持基本思路:
我已经参考 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 _Application
和 dispinterface _Workbook
没有在 Excel 类型库中设置此标志,dispinterface _Worksheet
有。
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) 在他对 Application
和 WorksheetFunction
的评论中已经给出了一些答案。对我来说,这是保持 Application
可扩展性的一个令人信服的理由,我不会进一步考虑 Application
。
让我们转向 Workbook
和 Worksheet
,我们最好从一些代码开始演示,因此您需要从两个新的工作簿开始,将它们命名为 MyWorkbook.xlsm
和 OtherWorkbook.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表格,已添加到项目中。