Bug when using interfaces on larger projects

前提是你 提交于 2021-01-17 06:53:18

问题


For larger VBA projects (40,000+ lines of code) I cannot properly use interfaces because the Application (I mainly use Excel) will crash quite often. Apparently, this is because the code cannot remain compiled (from my understanding VBA code gets compiled to P-code which is later interpreted). I mainly get this behavior when the VBA Project is password protected.

The Debug/Compile menu is almost never "greyed out" when I open the hosting document:

This article describes the same behavior. Go to section 2.3

For example:
IClass interface:

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

Code in standard module:

Option Explicit

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

As expected the Debug.Print obj.SomeProperty line works fine and prints 0 in the Immediate window if the project is small. However, on a large project the application crashes when this line is called. The IClass_SomeProperty is not reached (logging to a file clarifies this).

As in the above-mentioned article, there are ways to temporarily avoid the issue:

  1. Recompiling solves the issue (not always) but the crash could occur on the very next time the document is opened, or it might keep things going for a few days (presuming file is opened every day)
  2. When option 1 is not working, deactivating all Implements statements in the entire project using the Find/Replace window, then compiling and then reactivating the statements back. Again, this can work for a few hours or a few days but inevitably a crash will occur in the near future

Since the VBA Project is password protected and because many people are using these VBA-capable documents (Workbooks in my case), applying the temporary fixes does not help at all.

The only way that I've found to avoid the crashes and still get the benefits of the interfaces is to use conditional compilation. Basically, I use interfaces only for development and then switch to late-binding for production. Obviously, this comes with a lot of headaches.

The example above becomes:
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

Notice that all interface methods must be duplicated and made public so that late-binding is an option.

Code in standard module:

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

When developing new features, I follow these steps:

  1. turn #Const USE_INTERFACES = True for all occurrences using Find/Replace
  2. add new features
  3. turn #Const USE_INTERFACES = False so that code runs on late-binding and doesn't crash

I've been experiencing this bug for at least 3 years. I would obviously avoid the conditional compilation workaround if I could.

Is there a way to keep the VBA Project compiled (let's say running a procedure when opening the document) without access to the VBA project object model? It is not an option for me to have the Trust access to the VBA project object model turned on.

I appreciate this bug is not easy to re-create unless you happen to have a large VBA project at hand.

EDIT 1

A nice point raised by @PEH in the comments: this issue is applicable for both xlsm and xlsb files (Excel).


回答1:


I don't have a 40K-liner project to test it, but it is known that the VBE debugger's edit-and-continue feature can corrupt the internal storage streams, resulting in "ghost breakpoints".

The VBE itself will corrupt a VBA project, given enough time and enough debugger sessions (and the right amount of pixie dust) - with or without interfaces involved.

I've been coding against interfaces in VBA ever since I've realized that VBA had that capability, and the only problem I've found that was directly caused by Implements statements, was that if you make a document module (e.g. Sheet1, or ThisWorkbook), implement an interface, then you're 100% going to corrupt your project and crash Excel. But with normal user classes? Nope, never had a problem with those.

I would definitely consider edit-and-continue the primary suspect for any stream corruption happening in a VBA project - if the internal ITypeInfo gets corrupted when the type Implements an interface, then that would be because of the bug with edit-and-continue, and the Implements statement "causing it" is really just a symptom.

Project size is also an important factor: 40K LoC is indeed a very large VBA project, and large VBA projects have a tendency to more easily get corrupted too. This can usually be mitigated by regularly removing+exporting all code files and re-importing them back into the project (which forces a "clean rebuild" of the internal storage, I'm guessing) - if your very large project is under source control, then this should be happening regularly.

The VBE does not want you to write OOP. It's actively fighting it: it wants you to cram as much code as possible into as few modules as possible, and since its navigation tooling objectively sucks, you don't get a "find all implementations" command to quickly locate the concrete implementations for your interfaces, so yes the VBE's "go to definition" takes you to an empty method stub - guess what, "go to definition" in Microsoft Visual Studio 2019 does exactly the same (it does give you "go to implementation" too though).

Writing OOP in VBA without Rubberduck to assist with navigation (and everything else) isn't something I would recommend, but then irony has it that Rubberduck doesn't perform very well with very large legacy code bases with lots of late binding (implicit or not).

If project size is related to the problem, then using conditional compilation is actually making things worse, by making the project even larger, and then making Rubberduck fail to "see" pretty much half of the code (late bound member calls can't be resolved, so we lose track of what's being used where), ...which essentially cripples it.

I don't have a solution, only a hunch that edit-and-continue is very likely behind this, since it rewrites chunks of p-code on the fly, and that is already known to be causing problems. If my hunch is correct, then regularly exporting & reimporting all code files should help keep the corruption at bay (and then it makes it easy to put the project under source control, should anything ever become irreparably broken). Avoid code-behind in document modules as much as possible when you do this, and whatever you do NEVER make a worksheet/document module Implements any interface. Rubberduck has tooling to quickly & easily export/import multiple code files at once to/from a given folder.




回答2:


I too jumped into using interfaces (years ago) because I thought they were proper COM (and they are) but I too hit problems. So, I stopped using them.

Apart from anything else, if you double click on a method with normal code you jump to the code implementation but with interfaces you jump to an empty method (which is unsatisfactory).

If you really have a class, Foo, that expresses different behaviours worthy enough to break into a separate interface IBar then why not break IBar into an actual separate class Bar and then set an instance of Bar to be a public property of Foo?

That's just a suggestion. I know of no other VBA fix and it is extremely unlikely Microsoft will fix this now.

You could always migrate your code to VisualBasic.Net if you want to preserve you class/interface design.

I'd be delighted if someone actually solves this.



来源:https://stackoverflow.com/questions/63989741/bug-when-using-interfaces-on-larger-projects

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!