【问题标题】:How do I Late Bind a Class Module from my Project?如何后期绑定项目中的类模块?
【发布时间】:2021-02-17 06:24:02
【问题描述】:

我的项目中有两个类模块,“ClassAlfa”和“ClassBeta”。我已经将两个类之间的代码分开,这样如果最终用户的项目中没有包含 ClassBeta,那么 ClassAlfa 对它的任何后期绑定引用都会优雅地失败。如果最终用户确实有可用的 ClassBeta,我遇到的问题是使脚本工作。

在 ClassAlfa 中,我有以下代码:

Option Explicit

Public myClass as Object

Private Sub Class_Initialize()
    On Error Resume Next
    Set myClass = Application.Run("ClassBeta")
    If Err = 0 Then 'End user has this class available, go ahead and use it.
        'Do code with ClassBeta
    Else
        'Do code without ClassBeta
    End If
    On Error GoTo 0
End Sub

这会在Set 行引发以下错误:

运行时错误“1004”:

无法运行宏“ClassBeta”。该工作簿中的宏可能不可用,或者所有宏都可能被禁用。

我也试过用这个替换 Set 行:

Set myClass = CreateObject("ClassBeta")

反而会引发错误

运行时错误“429”:

ActiveX 组件无法创建对象

也不起作用:

Set myClass = CreateObject("'" & ThisWorkbook.Name & "'!" & "ClassBeta")

从我自己的项目中后期绑定自定义类的正确方法是什么?

【问题讨论】:

  • 你在设置中设置的所有错误都有中断吗?
  • @QHarr,否定的。如果该类实际存在,我不希望出现错误。如果该模块不存在,则代码将按需要运行。
  • 您不能在应用程序运行中使用 B 类。这就是为什么当它在那里时你会收到错误,因为 VBA 不知道如何“运行”B 类,因为它不是一种方法。
  • @SandPiper 这个question 与您的问题相似,可能会给您一些线索。

标签: excel vba class object


【解决方案1】:

VBA 中没有任何机制可以让您通过名称检查类是否存在,如果存在则创建一个新实例。

但是,有可能实现。让我们剖析这些问题,看看我们如何解决这些问题。

问题 1

您需要创建一个您不确定当前项目中是否存在的类的实例。怎么样?

正如您已经尝试过的,Application.Run 和 CreateObject 不起作用。

Application.Run 只能在标准 .bas 模块(不是类模块)中运行方法。它不创建类的实例。

CreateObject 在幕后做了一些事情。首先,它使用您传递的 ProgID(字符串)调用CLSIDFromProgIDEx。然后,它调用CoCreateInstance。问题是 VBA 类在注册表中没有它们的 ProgID,因此 CreateObject 根本不起作用。您需要将您的课程放在 ActiveX.exe 的已注册 .dll 文件中才能完成这项工作。

剩下的就是New 关键字来实例化一个新的ClassBeta。这显然在类存在时有效,但在不存在时会给出“用户定义的类型未定义”编译器错误。只有 2 种方法可以抑制此编译器错误:

  1. 有一个编译器指令
  2. 根本没有New ClassBeta

ClassAlfa 中的编译器指令如下所示:

Option Explicit

#Const BETA_EXISTS = False

Sub Test()
    Dim myBeta As Object
    #If BETA_EXISTS Then
        Set myBeta = New ClassAlpha
    #End If
    Debug.Print "Type of 'myBeta' is: " & TypeName(myBeta)
End Sub

如果项目中没有ClassBeta,这编译得很好,但是你永远不能让BETA_EXISTSconditional compiler constant变成True,因为:

表达式中只能使用条件编译器常量和文字

因此,最后一个选项是不要在项目中的任何位置使用New ClassBeta。除了,我们这样做。我们可以把它放在ClassBeta本身作为一个工厂:

Option Explicit

Public Function Factory() As ClassBeta
    Set Factory = New ClassBeta
End Function

当类丢失时,工厂丢失,New ClassBeta 不会抛出编译器错误。

问题 2

我们如何在ClassBeta 上调用.Factory 方法?

好吧,我们显然不能创建ClassBeta 的新实例,因为这是我们的第一个问题。

我们可以做的是确保ClassBeta 总是有一个默认的全局实例(就像用户表单一样)。为此,我们需要将类导出到 .cls 文本文件并使用文本编辑器(如记事本)对其进行编辑。文本应如下所示:

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "ClassBeta"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit

Public Function Factory() As ClassBeta
    Set Factory = New ClassBeta
End Function

请注意,我已将 VB_PredeclaredId 属性更改为 True(在文本编辑器中手动更改)。现在我们可以将类导入回来。我们可以通过在即时窗口中输入?Typename(ClassBeta.Factory) 然后按 Enter 来检查它是否有效。我们应该看到:

ClassAlfa 中的代码现在可以写成:

Option Explicit

Sub Test()
    Dim myBeta As Object

    On Error Resume Next
    Set myBeta = ClassBeta.Factory
    On Error GoTo 0

    Debug.Print "Type of 'myBeta' is: " & TypeName(myBeta)
End Sub

问题 3

如果我们从项目中删除ClassBeta,以下行将无法编译:

Set myBeta = ClassBeta.Factory

但是,与问题1相比,这次编译器错误是'Variable not defined'。

我想摆脱这个新的编译器错误的唯一方法是关闭Option Explicit。是的!太糟糕了!

ClassAlfa:

'Option Explicit 'needs to be off to be able to compile without the ClassBeta class

Sub Test()
    Dim myBeta As Object

    On Error Resume Next
    Set myBeta = ClassBeta.Factory
    On Error GoTo 0

    Debug.Print "Type of 'myBeta' is: " & TypeName(myBeta)
End Sub

最后的想法

您可以在 Option Explicit 开启时进行开发,然后为您的用户关闭它。

如果您的用户将使用ClassAlfa 并关闭Option Explicit,则代码应该可以正常工作。但如果他们也想要ClassBeta,那么他们获得它的唯一方法就是导入 .cls 文件。复制粘贴代码不起作用,因为它们会丢失ClassBeta 的全局实例(我们在VB_PredeclaredId 隐藏属性中设置的实例)。

我必须说我不建议删除Option Explicit,因为这可能会导致其他问题。我认为没有办法以“干净”的方式实现您想要的。

【讨论】:

  • 这是一个可靠的答案,谢谢。我最终从 ClassBeta 中获取了功能并将其复制到 ClassAlfa 中。不适合 DRY 原则,但这是我必须采用的解决方法。
  • @SandPiper 如果你不介意我好奇,你用它们做什么?
  • 完全没有,谢谢你的提问。我正在开发我的第一个开源项目 (github.com/M-Scott-Lassiter/Fred-the-Developer)。我希望在本周末达到 1.0 版。我将它从我正在从事的另一个项目中剥离出来。
  • @SandPiper 很有趣。我还有一个我从未发布过的代码管理器和几个 100% VBA 工具,其中一些在 GitHub 上。在 Mac 上工作时,KeyedCollection 是 Scripting.Dictionary 的一个很好的替代品,但我猜你还有其他依赖项,比如可扩展性库。实际上,我可能会在 GitHub 上与您联系,讨论后者(希望我这周晚些时候有时间)。
猜你喜欢
  • 2021-12-18
  • 2015-05-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-13
  • 1970-01-01
  • 1970-01-01
  • 2020-07-25
相关资源
最近更新 更多