22

几个月前我在 VBA 中发现了一个错误,但找不到合适的解决方法。这个错误真的很烦人,因为它限制了一个很好的语言功能。

使用自定义集合类时,希望有一个枚举器以便可以在For Each循环中使用该类是很常见的。这可以通过添加以下行来完成:

Attribute [MethodName].VB_UserMemId = -4 'The reserved DISPID_NEWENUM

在函数/属性签名行之后立即通过:

  1. 导出类模块,在文本编辑器中编辑内容,然后重新导入
  2. 在函数签名上方使用Rubberduck注解'@Enumerator,然后进行同步

不幸的是,在 x64 上,使用上述特性会导致写入错误的内存,并在某些情况下导致应用程序崩溃(稍后讨论)。

重现错误

CustomCollection班级:

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

Private m_coll As Collection

Private Sub Class_Initialize()
    Set m_coll = New Collection
End Sub
Private Sub Class_Terminate()
    Set m_coll = Nothing
End Sub

Public Sub Add(v As Variant)
    m_coll.Add v
End Sub

Public Function NewEnum() As IEnumVARIANT
Attribute NewEnum.VB_UserMemId = -4
    Set NewEnum = m_coll.[_NewEnum]
End Function

标准模块中的代码:

Option Explicit

Sub Main()
    #If Win64 Then
        Dim c As New CustomCollection
        c.Add 1
        c.Add 2
        ShowBug c
    #Else
        MsgBox "This bug does not occur on 32 bits!", vbInformation, "Cancelled"
    #End If
End Sub

Sub ShowBug(c As CustomCollection)
    Dim ptr0 As LongPtr
    Dim ptr1 As LongPtr
    Dim ptr2 As LongPtr
    Dim ptr3 As LongPtr
    Dim ptr4 As LongPtr
    Dim ptr5 As LongPtr
    Dim ptr6 As LongPtr
    Dim ptr7 As LongPtr
    Dim ptr8 As LongPtr
    Dim ptr9 As LongPtr
    '
    Dim v As Variant
    '
    For Each v In c
    Next v
    Debug.Assert ptr0 = 0
End Sub

通过运行该Main方法,代码将停在Assert该方法的行上ShowBug,您可以在Locals窗口中看到,局部变量的值不知从何处发生了变化: 其中 ptr1 等于. 方法中使用的变量越多(包括可选参数),方法中的 ptrs 越多,写入的值(内存地址)就越多。
在此处输入图像描述
ObjPtr(c)NewEnumShowBug

不用说,删除方法中的本地ptr变量ShowBug肯定会导致应用程序崩溃。

逐行单步执行代码时,不会出现此错误!


更多关于这个错误

该错误与实际Collection存储在CustomCollection. 调用 NewEnum 函数后立即写入内存。因此,基本上执行以下任何操作都无济于事(经过测试):

  1. 添加Optional参数
  2. 从函数中删除所有代码(参见下面的代码)
  3. 声明为IUnknown而不是IEnumVariant
  4. 而不是Function声明为Property Get
  5. 在方法签名中使用类似Friendor的关键字Static
  6. 将 DISPID_NEWENUM 添加到 Get 的LetSet对应项,甚至隐藏前者(即,将 Let/Set 设为私有)。

让我们尝试上面提到的第 2 步。如果CustomCollection变成:

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

Public Function NewEnum() As IEnumVARIANT
Attribute NewEnum.VB_UserMemId = -4
End Function

并且用于测试的代码更改为:

Sub Main()
    #If Win64 Then
        Dim c As New CustomCollection
        ShowBug c
    #Else
        MsgBox "This bug does not occur on 32 bits!", vbInformation, "Cancelled"
    #End If
End Sub

Sub ShowBug(c As CustomCollection)
    Dim ptr0 As LongPtr
    Dim ptr1 As LongPtr
    Dim ptr2 As LongPtr
    Dim ptr3 As LongPtr
    Dim ptr4 As LongPtr
    Dim ptr5 As LongPtr
    Dim ptr6 As LongPtr
    Dim ptr7 As LongPtr
    Dim ptr8 As LongPtr
    Dim ptr9 As LongPtr
    '
    Dim v As Variant
    '
    On Error Resume Next
    For Each v In c
    Next v
    On Error GoTo 0
    Debug.Assert ptr0 = 0
End Sub

运行Main会产生相同的错误。

解决方法

我发现避免该错误的可靠方法:

  1. 调用一个方法(基本上是离开ShowBug方法)然后回来。这需要在For Each执行该行之前发生(之前意味着它可以在同一方法中的任何位置,不一定是之前的确切行):

    Sin 0 'Or VBA.Int 1 - you get the idea
    For Each v In c
    Next v
    

    缺点:容易忘记

  2. 做一个Set声明。它可能在循环中使用的变体上(如果没有使用其他对象)。与上面的第 1 点一样,这需要在For Each执行该行之前发生:

    Set v = Nothing
    For Each v In c
    Next v
    

    甚至通过使用Set c = c
    Or 将集合设置为自身,将c参数传递ByValShowBug方法(作为 Set,调用 IUnknown::AddRef)
    缺点:容易忘记

  3. 使用一个单独的EnumHelper类,它是唯一用于枚举的类:

    VERSION 1.0 CLASS
    BEGIN
      MultiUse = -1  'True
    END
    Attribute VB_Name = "EnumHelper"
    Attribute VB_GlobalNameSpace = False
    Attribute VB_Creatable = False
    Attribute VB_PredeclaredId = False
    Attribute VB_Exposed = False
    Option Explicit
    
    Private m_enum As IEnumVARIANT
    
    Public Property Set EnumVariant(newEnum_ As IEnumVARIANT)
        Set m_enum = newEnum_
    End Property
    Public Property Get EnumVariant() As IEnumVARIANT
    Attribute EnumVariant.VB_UserMemId = -4
        Set EnumVariant = m_enum
    End Property
    

    CustomCollection会成为:

    VERSION 1.0 CLASS
    BEGIN
      MultiUse = -1  'True
    END
    Attribute VB_Name = "CustomCollection"
    Attribute VB_GlobalNameSpace = False
    Attribute VB_Creatable = False
    Attribute VB_PredeclaredId = False
    Attribute VB_Exposed = False
    Option Explicit
    
    Private m_coll As Collection
    
    Private Sub Class_Initialize()
        Set m_coll = New Collection
    End Sub
    Private Sub Class_Terminate()
        Set m_coll = Nothing
    End Sub
    
    Public Sub Add(v As Variant)
        m_coll.Add v
    End Sub
    
    Public Function NewEnum() As EnumHelper
        Dim eHelper As New EnumHelper
        '
        Set eHelper.EnumVariant = m_coll.[_NewEnum]
        Set NewEnum = eHelper
    End Function
    

    和调用代码:

    Option Explicit
    
    Sub Main()
        #If Win64 Then
            Dim c As New CustomCollection
            c.Add 1
            c.Add 2
            ShowBug c
        #Else
            MsgBox "This bug does not occur on 32 bits!", vbInformation, "Cancelled"
        #End If
    End Sub
    
    Sub ShowBug(c As CustomCollection)
        Dim ptr0 As LongPtr
        Dim ptr1 As LongPtr
        Dim ptr2 As LongPtr
        Dim ptr3 As LongPtr
        Dim ptr4 As LongPtr
        Dim ptr5 As LongPtr
        Dim ptr6 As LongPtr
        Dim ptr7 As LongPtr
        Dim ptr8 As LongPtr
        Dim ptr9 As LongPtr
        '
        Dim v As Variant
        '
        For Each v In c.NewEnum
            Debug.Print v
        Next v
        Debug.Assert ptr0 = 0
    End Sub
    

    显然,保留的 DISPID 已从CustomCollection类中删除。

    优点:强制For Eachon.NewEnum函数而不是直接自定义集合。这避免了由错误引起的任何崩溃。

    缺点:总是需要额外的EnumHelper课程。很容易忘记在行中添加.NewEnumFor Each只会触发运行时错误)。

最后一种方法(3)之所以有效,是因为在c.NewEnum执行该ShowBug方法时退出,然后在调用类Property Get EnumVariant内部之前返回EnumHelper。基本上方法(1)是避免错误的方法。


这种行为的解释是什么?能否以更优雅的方式避免此错误?

编辑

通过CustomCollectionByVal 并不总是一种选择。考虑一个Class1

Option Explicit

Private m_collection As CustomCollection

Private Sub Class_Initialize()
    Set m_collection = New CustomCollection
End Sub
Private Sub Class_Terminate()
    Set m_collection = Nothing
End Sub

Public Sub AddElem(d As Double)
    m_collection.Add d
End Sub

Public Function SumElements() As Double
    Dim v As Variant
    Dim s As Double
    
    For Each v In m_collection
        s = s + v
    Next v
    SumElements = s
End Function

现在是一个调用例程:

Sub ForceBug()
    Dim c As Class1
    Set c = New Class1
    c.AddElem 2
    c.AddElem 5
    c.AddElem 7
    
    Debug.Print c.SumElements 'BOOM - Application crashes
End Sub

显然,这个例子有点勉强,但是有一个包含“子”对象的自定义集合的“父”对象是很常见的,并且“父”可能想要执行一些涉及部分或全部“子”的操作。

在这种情况下,很容易忘记在行Set前执行语句或方法调用For Each

4

2 回答 2

10

怎么了

看起来堆栈帧是重叠的,尽管它们不应该重叠。方法中有足够的变量ShowBug可以防止崩溃,并且变量的值(在调用者子例程中)被简单地更改,因为它们引用的内存也被另一个堆栈帧(被调用的子例程)使用,该堆栈帧稍后在调用堆栈的顶部。

Debug.Print我们可以通过向问题中的相同代码添加几个语句来测试这一点。

CustomCollection班级:

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

Private m_coll As Collection

Private Sub Class_Initialize()
    Set m_coll = New Collection
End Sub
Private Sub Class_Terminate()
    Set m_coll = Nothing
End Sub

Public Sub Add(v As Variant)
    m_coll.Add v
End Sub

Public Function NewEnum() As IEnumVARIANT
Attribute NewEnum.VB_UserMemId = -4
    Debug.Print "The NewEnum return address " & VarPtr(NewEnum) & " should be outside of the"
    Set NewEnum = m_coll.[_NewEnum]
End Function

调用代码,在标准的 .bas 模块中:

Option Explicit

Sub Main()
    #If Win64 Then
        Dim c As New CustomCollection
        c.Add 1
        c.Add 2
        ShowBug c
    #Else
        MsgBox "This bug does not occur on 32 bits!", vbInformation, "Cancelled"
    #End If
End Sub

Sub ShowBug(ByRef c As CustomCollection)
    Dim ptr0 As LongPtr
    Dim ptr1 As LongPtr
    Dim ptr2 As LongPtr
    Dim ptr3 As LongPtr
    Dim ptr4 As LongPtr
    Dim ptr5 As LongPtr
    Dim ptr6 As LongPtr
    Dim ptr7 As LongPtr
    Dim ptr8 As LongPtr
    Dim ptr9 As LongPtr
    '
    Dim v As Variant
    '
    For Each v In c
    Next v
    Debug.Print VarPtr(ptr9) & " - " & VarPtr(ptr0) & " memory range"
    Debug.Assert ptr0 = 0
End Sub

通过运行,Main我在即时窗口中得到了类似的东西:
在此处输入图像描述

返回值的地址NewEnum显然位于方法的ptr0ptr9变量之间的内存地址ShowBug。所以,这就是为什么变量会不知从哪里获取值,因为它们实际上来自NewEnum方法的堆栈帧(例如对象的 vtable 的地址或IEnumVariant接口的地址)。如果变量不存在,那么崩溃很明显,因为内存的更关键部分正在被覆盖(例如,ShowBug方法的帧指针地址)。由于该NewEnum方法的堆栈帧较大(例如,我们可以添加局部变量来增加大小),因此在调用堆栈中顶部堆栈帧和下面的堆栈帧之间共享的内存越多。

如果我们使用问题中描述的选项解决该错误会发生什么?只需在行Set v = Nothing前添加一个For Each v In c,结果为:
在此处输入图像描述

显示前一个值和当前值(蓝色边框),我们可以看到返回位于方法的和变量NewEnum之外的内存地址。似乎使用解决方法正确分配了堆栈帧。ptr0ptr9ShowBug

如果我们NewEnum在调用堆栈内部中断,如下所示:
在此处输入图像描述

如何For Each调用NewEnum

每个 VBA 类都派生自IDispatch(而 IDispatch 又派生自 IUnknown)。

For Each...在一个对象上调用一个循环时,该对象的IDispatch::Invoke方法被调用为dispIDMember等于 -4。VBA.Collection 已经有这样的成员,但是对于 VBA 自定义类,我们用标记我们自己的方法,Attribute NewEnum.VB_UserMemId = -4以便 Invoke 可以调用我们的方法。

Invoke如果该For Each行中使用的接口不是从 派生的,则不会直接调用IDispatch。相反,IUnknown::QueryInterface首先调用并要求提供 IDispatch 接口。在这种情况下Invoke,显然只有在返回 IDispatch 接口后才会调用。这就是为什么For Each在声明的对象上使用As IUnknown不会导致错误的原因,无论它是否被传递ByRef,或者它是全局或类成员自定义集合。它只是使用问题中提到的解决方法 1(即调用另一种方法),尽管我们看不到它。

挂钩调用

我们可以用我们自己的一种方法替换非 VBInvoke方法,以便进一步调查。在标准.bas模块中,我们需要以下代码来挂钩:

Option Explicit

#If Mac Then
    #If VBA7 Then
        Private Declare PtrSafe Function CopyMemory Lib "/usr/lib/libc.dylib" Alias "memmove" (Destination As Any, Source As Any, ByVal Length As LongPtr) As LongPtr
    #Else
        Private Declare Function CopyMemory Lib "/usr/lib/libc.dylib" Alias "memmove" (Destination As Any, Source As Any, ByVal Length As Long) As Long
    #End If
#Else 'Windows
    'https://msdn.microsoft.com/en-us/library/mt723419(v=vs.85).aspx
    #If VBA7 Then
        Public Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
    #Else
        Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    #End If
#End If

#If Win64 Then
    Private Const PTR_SIZE As Long = 8
#Else
    Private Const PTR_SIZE As Long = 4
#End If

#If VBA7 Then
    Private newInvokePtr As LongPtr
    Private oldInvokePtr As LongPtr
    Private invokeVtblPtr As LongPtr
#Else
    Private newInvokePtr As Long
    Private oldInvokePtr As Long
    Private invokeVtblPtr As Long
#End If

'https://docs.microsoft.com/en-us/windows/win32/api/oaidl/nf-oaidl-idispatch-invoke
Function IDispatch_Invoke(ByVal this As Object _
    , ByVal dispIDMember As Long _
    , ByVal riid As LongPtr _
    , ByVal lcid As Long _
    , ByVal wFlags As Integer _
    , ByVal pDispParams As LongPtr _
    , ByVal pVarResult As LongPtr _
    , ByVal pExcepInfo As LongPtr _
    , ByRef puArgErr As Long _
) As Long
    Const DISP_E_MEMBERNOTFOUND = &H80020003
    '
    Debug.Print "The IDispatch::Invoke return address " & VarPtr(IDispatch_Invoke) & " should be outside of the"
    IDispatch_Invoke = DISP_E_MEMBERNOTFOUND
End Function

Sub HookInvoke(obj As Object)
    If obj Is Nothing Then Exit Sub
    #If VBA7 Then
        Dim vTablePtr As LongPtr
    #Else
        Dim vTablePtr As Long
    #End If
    '
    newInvokePtr = VBA.Int(AddressOf IDispatch_Invoke)
    CopyMemory vTablePtr, ByVal ObjPtr(obj), PTR_SIZE
    '
    invokeVtblPtr = vTablePtr + 6 * PTR_SIZE
    CopyMemory oldInvokePtr, ByVal invokeVtblPtr, PTR_SIZE
    CopyMemory ByVal invokeVtblPtr, newInvokePtr, PTR_SIZE
End Sub

Sub RestoreInvoke()
    If invokeVtblPtr = 0 Then Exit Sub
    '
    CopyMemory ByVal invokeVtblPtr, oldInvokePtr, PTR_SIZE
    invokeVtblPtr = 0
    oldInvokePtr = 0
    newInvokePtr = 0
End Sub

我们运行Main2方法(标准 .bas 模块)来产生错误:

Option Explicit

Sub Main2()
    #If Win64 Then
        Dim c As Object
        Set c = New CustomCollection
        c.Add 1
        c.Add 2
        '
        HookInvoke c
        ShowBug2 c
        RestoreInvoke
    #Else
        MsgBox "This bug does not occur on 32 bits!", vbInformation, "Cancelled"
    #End If
End Sub

Sub ShowBug2(ByRef c As CustomCollection)
    Dim ptr00 As LongPtr
    Dim ptr01 As LongPtr
    Dim ptr02 As LongPtr
    Dim ptr03 As LongPtr
    Dim ptr04 As LongPtr
    Dim ptr05 As LongPtr
    Dim ptr06 As LongPtr
    Dim ptr07 As LongPtr
    Dim ptr08 As LongPtr
    Dim ptr09 As LongPtr
    Dim ptr10 As LongPtr
    Dim ptr11 As LongPtr
    Dim ptr12 As LongPtr
    Dim ptr13 As LongPtr
    Dim ptr14 As LongPtr
    Dim ptr15 As LongPtr
    Dim ptr16 As LongPtr
    Dim ptr17 As LongPtr
    Dim ptr18 As LongPtr
    Dim ptr19 As LongPtr
    '
    Dim v As Variant
    '
    On Error Resume Next
    For Each v In c
    Next v
    Debug.Print VarPtr(ptr19) & " - " & VarPtr(ptr00) & " range on the call stack"
    Debug.Assert ptr00 = 0
End Sub

请注意,需要更多的虚拟 ptr 变量来防止崩溃,因为堆栈帧IDispatch_Invoke更大(因此,内存重叠更大)。

通过运行上述内容,我得到:
在此处输入图像描述

NewEnum尽管由于方法的挂钩,代码永远不会到达方法,但也会发生相同的错误Invoke。堆栈帧再次被错误分配。

同样,Set v = Nothing在结果之前添加一个For Each v In c在此处输入图像描述

堆栈帧已正确分配(绿色边框)。这表明问题不在于NewEnum方法,也不在于我们的替换Invoke方法。Invoke在我们被调用之前发生了一些事情。

如果我们IDispatch_Invoke在调用堆栈内部中断,则如下所示:
在此处输入图像描述

最后一个例子。考虑一个空白(没有代码)类Class1。如果我们运行Main3以下代码:

Option Explicit

Sub Main3()
    #If Win64 Then
        Dim c As New Class1
        ShowBug3 c
    #Else
        MsgBox "This bug does not occur on 32 bits!", vbInformation, "Cancelled"
    #End If
End Sub

Sub ShowBug3(ByRef c As Class1)
    Dim ptr0 As LongPtr
    Dim ptr1 As LongPtr
    Dim ptr2 As LongPtr
    Dim ptr3 As LongPtr
    Dim ptr4 As LongPtr
    Dim ptr5 As LongPtr
    Dim ptr6 As LongPtr
    Dim ptr7 As LongPtr
    Dim ptr8 As LongPtr
    Dim ptr9 As LongPtr
    '
    Dim v As Variant
    '
    On Error Resume Next
    For Each v In c
    Next v
    Debug.Assert ptr0 = 0
End Sub

该错误根本不会发生。Main2这与使用我们自己的 hooked运行有什么不同Invoke?在这两种情况下DISP_E_MEMBERNOTFOUND都返回并且不NewEnum调用任何方法。

好吧,如果我们并排查看前面显示的调用堆栈: 我们可以看到非 VB没有作为单独的“非基本代码”条目推送到 VB 堆栈上。
在此处输入图像描述
Invoke

显然,只有在调用 VBA 方法(通过原始的非 VB Invoke 或我们自己的 IDispatch_Invoke 的 NewEnum)时才会出现该错误。如果调用了非 VB 方法(如原始 IDispatch::Invoke 没有后续 NewEnum),则不会出现Main3上述错误。For Each...在相同情况下在 VBA 集合上运行时也不会出现错误。

错误原因

正如上面所有的例子所暗示的,这个错误可以总结为:
For Each调用IDispatch::InvokeNewEnum而堆栈指针没有随着ShowBug堆栈帧的大小而增加。ShowBug因此,两个框架(调用者和被调用者)使用相同的内存NewEnum

解决方法

强制堆栈指针正确递增的方法:

  1. 直接调用另一个方法(For Each在行之前),例如Sin 1
  2. 间接调用另一个方法(For Each在行之前):
    • IUnknown::AddRef通过传递参数调用ByVal
    • IUnknown::QueryInterface使用stdole.IUnknown接口调用
    • 使用将调用其中一个或两个的语句Set(例如)。也可以根据源和目标接口调用AddRefReleaseSet c = cQueryInterface

正如问题的编辑部分所建议的那样,我们并不总是有可能传递自定义集合类ByVal,因为它可能只是一个全局变量或类成员,我们需要记住做一个虚拟Set语句或在For Each...执行之前调用另一个方法。

解决方案

我仍然找不到比问题中提出的更好的解决方案,因此我将在此处复制代码作为答案的一部分,并稍作调整。

EnumHelper班级:

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

Private m_enum As IEnumVARIANT

Public Property Set EnumVariant(newEnum_ As IEnumVARIANT)
    Set m_enum = newEnum_
End Property
Public Property Get EnumVariant() As IEnumVARIANT
Attribute EnumVariant.VB_UserMemId = -4
    Set EnumVariant = m_enum
End Property

Public Property Get Self() As EnumHelper
    Set Self = Me
End Property

CustomCollection现在会变成这样:

Option Explicit

Private m_coll As Collection

Private Sub Class_Initialize()
    Set m_coll = New Collection
End Sub
Private Sub Class_Terminate()
    Set m_coll = Nothing
End Sub

Public Sub Add(v As Variant)
    m_coll.Add v
End Sub

Public Function NewEnum() As EnumHelper
    With New EnumHelper
        Set .EnumVariant = m_coll.[_NewEnum]
        Set NewEnum = .Self
    End With
End Function

你只需要打电话给For Each v in c.NewEnum

虽然,EnumHelper在任何实现自定义集合类的项目中,该类都是一个额外的类,但也有几个优点:

  1. 您永远不需要将其添加Attribute [MethodName].VB_UserMemId = -4 到任何其他自定义集合类。这对于没有安装RubberDuck(注释)的用户更有用'@Enumerator,因为他们需要为每个自定义集合类导出、编辑 .cls 文本文件并导入回来
  2. 您可以为同一个类公开多个 EnumHelper。考虑一个自定义字典类。你可以同时拥有一个ItemsEnum和一个KeysEnum。两者For Each v in c.ItemsEnumFor Each v in c.KeysEnum可以
  3. 您永远不会忘记使用上面介绍的解决方法之一,因为在调用成员 ID -4EnumHelper之前将调用公开类的方法Invoke
  4. 你不会再遇到崩溃了。如果您忘记调用 withFor Each v in c.NewEnum而是使用For Each v in c,您只会得到一个运行时错误,无论如何都会在测试中发现。当然,您仍然可以通过将结果传递c.NewEnum给另一个方法来强制崩溃,然后该方法ByRef需要For Each在任何其他方法调用或Set语句之前执行 a。你极不可能这样做
  5. 显而易见但值得一提的是,您将对EnumHelper项目中可能拥有的所有自定义集合类使用相同的类
于 2021-01-15T12:59:56.427 回答
1

由于没有足够的代表,我无法添加评论,也无法使用已冻结的聊天部分,但我想补充一点,我遇到了一些听起来非常相似的东西,尽管我还没有测试这里介绍的任何解决方案,似乎都是同一个错误。

我试图在这里描述它:

https://docs.microsoft.com/en-us/answers/questions/464383/is-the-vba-64-bit-compiler-broken.html?childToView=545565#answer-545565

我希望测试也能为我解决问题,如果是这样,我衷心感谢您调查问题并提供解决方法,否则意味着代码无法移植到 64 位 VBA。

于 2021-09-09T08:58:16.883 回答