在较大项目上使用接口时的错误

问题描述

对于较大的VBA项目(40,000行代码),我无法正确使用接口,因为应用程序(主要使用Excel)会经常崩溃。显然,这是因为代码无法保持编译状态(据我了解,VBA代码被编译为P代码,以后再进行解释)。当VBA项目受密码保护时,我主要会遇到这种情况。

打开托管文档时,“调试/编译”菜单几乎永远不会“灰显”:

enter image description here

article描述了相同的行为。转到第2.3节

例如:
IClass界面:

Option Explicit

Public Property Get SomeProperty() As Double
End Property

Class1

Option Explicit

Implements IClass

Private Property Get IClass_SomeProperty() As Double
    IClass_SomeProperty = 0
End Property

标准模块中的代码:

Option Explicit

Sub TestInterface()
    Dim obj As IClass
    
    Set obj = New Class1
    Debug.Print obj.SomeProperty 'Crashes here on large projects only
End Sub

按预期,Debug.Print obj.SomeProperty行工作正常,如果项目较小,则在“立即”窗口中显示0。但是,在大型项目中,调用此行时,应用程序崩溃。未达到IClass_SomeProperty(登录到文件可以澄清这一点)。

与上述文章一样,有一些方法可以暂时避免该问题:

  1. 重新编译可以解决此问题(并非总是如此),但是崩溃可能在下次打开文档时发生,或者可能会使事情持续几天(假定每天都打开文件)
  2. 在当前选项1不工作,使用查找停用所有Implements在整个项目中陈述/替换窗口,然后编译,然后重新激活语句后面。再说一次,这可以工作几个小时或几天,但是不可避免地会在不久的将来发生崩溃

由于VBA项目受密码保护,并且由于许多人正在使用这些具有VBA功能的文档(在我的情况下为工作簿),因此应用临时修补程序根本无济于事。

我发现避免崩溃并仍然获得接口好处的唯一方法是使用条件编译。基本上,我只将接口用于开发,然后切换到后期绑定进行生产。显然,这会带来很多麻烦。

上面的示例变为:
Class1

Option Explicit

#Const USE_INTERFACES = True

#If USE_INTERFACES Then
Implements IClass
#End If

Private Property Get IClass_SomeProperty() As Double
    IClass_SomeProperty = Me.SomeProperty
End Property

Public Property Get SomeProperty() As Double
    SomeProperty = 0
End Property

请注意,所有接口方法都必须复制并公开,以便可以选择后期绑定。

标准模块中的代码:

Option Explicit

#Const USE_INTERFACES = True

Sub TestInterface()
    #If USE_INTERFACES Then
        Dim obj As IClass
    #Else
        Dim obj As Object
    #End If
    
    Set obj = New Class1
    Debug.Print obj.SomeProperty
End Sub

开发新功能时,请按照以下步骤操作:

  1. 使用“查找/替换”为所有事件转#Const USE_INTERFACES = True
  2. 添加新功能
  3. 转动#Const USE_INTERFACES = False,以使代码在后期绑定上运行并且不会崩溃

我已经遇到此错误至少三年了。如果可以的话,我显然会避免有条件的编译方法。

是否可以在不访问 VBA项目对象模型的情况下保持VBA项目的编译状态(比如打开文档时运行一个过程)?对我来说,不能启用对VBA项目对象模型的信任访问权限

除非您碰巧手头有一个大型VBA项目,否则我不容易重新创建此错误。

编辑1

@PEH在评论中提出的一个不错的观点:此问题适用于xlsmxlsb文件(Excel)。

解决方法

我没有40K线性项目进行测试,但是众所周知,VBE调试器的 edit-and-continue 功能会破坏内部存储流,从而导致“虚假断点”。 “。

如果有足够的时间和足够的调试器会话(以及适当数量的pixie尘埃),VBE本身将破坏VBA项目-不论是否包含接口。

自从我意识到VBA具有该功能以来,我就一直在针对VBA中的接口进行编码,而我发现的唯一问题是直接由Implements语句引起的 ,就是如果您制作一个文档模块(例如Sheet1ThisWorkbook),实现一个接口,那么您将100%破坏您的项目并使Excel崩溃。但是使用普通的用户类别?不,这些从来没有问题。

我绝对会认为编辑并继续是VBA项目中流损坏的主要嫌疑人-如果内部ITypeInfo在类型Implements时损坏一个界面,那是因为 edit-and-continue 的错误,而Implements语句“ cause it”实际上只是一个症状。

项目规模也是一个重要因素:40K LoC确实是一个非常大的VBA项目,并且大型VBA项目也倾向于更容易遭到破坏。通常可以通过定期删除+导出所有代码文件并将其重新导入到项目中来缓解这种情况(我猜这将强制对内部存储进行“干净的重建”)-如果您的大型项目处于源代码控制之下,那么该应该定期发生。

VBE不希望您编写OOP。它正在积极地与之抗争:它希望您将尽可能多的代码填充到尽可能少的模块中,并且由于其导航工具客观地很烂,因此您不会获得“查找所有实现”命令来快速找到您的具体实现接口,所以是的,VBE的“转到定义”将您带到一个空的方法存根-猜一下,Microsoft Visual Studio 2019中的“转到定义”功能完全相同(尽管它确实也为您提供了“转到实现”)。

我不建议在没有Rubberduck的情况下在VBA中编写OOP来辅助导航(以及其他所有操作),但是具有讽刺意味的是,Rubberduck在具有大量后期绑定的大型旧代码库中表现不佳(是否隐含)。

如果项目规模与问题有关,那么使用条件编译实际上会使事情变得更糟,这会使项目变得更大,然后让Rubberduck无法“看到”大部分代码(无法解析后期绑定的成员调用,因此我们无法跟踪在何处使用了什么),...实际上会破坏代码。

我没有解决方案,只是预感 edit-and-continue 可能在此背后,因为它会即时重写p代码块,并且这已经众所周知引起问题。如果我的预见是正确的,那么定期导出和重新导入所有代码文件应该有助于防止损坏(然后,如果有任何无法修复的损坏,则可以很容易地将项目置于源代码控制之下)。执行此操作时,请尽量避免在文档模块中隐藏代码,并且从不执行任何操作都应使工作表/文档模块Implements成为任何接口。 Rubberduck的工具可以快速轻松地一次将多个代码文件导出到给定的文件夹,或从该文件夹导入。

,

我也跳槽了使用接口(几年前),因为我认为它们是正确的COM(并且确实如此),但我也遇到了问题。所以,我不再使用它们。

除了别的以外,如果您双击具有正常代码的方法,则会跳至代码实现,而对于具有接口的接口,则会跳至一个空方法(效果不理想)。

如果您确实有一个类Foo,它表示足以将其拆分为单独的接口IBar的不同行为,那么为什么不将IBar拆分为实际的单独的类Bar,然后将Bar的实例设置为Foo的公共属性?

那只是一个建议。我知道没有其他VBA修复程序,Microsoft现在极不可能修复此问题。

如果要保留类/界面设计,可以始终将代码迁移到VisualBasic.Net。

如果有人真的解决了这个问题,我会感到很高兴。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...