programing

x64 사용자 지정 클래스의 각 열거에 대한 버그

linuxpc 2023. 4. 11. 21:46
반응형

x64 사용자 지정 클래스의 각 열거에 대한 버그

몇 달 전에 VBA에서 버그를 발견했는데 적절한 해결 방법을 찾을 수 없었습니다.그 버그는 좋은 언어 기능을 제한하기 때문에 정말 짜증난다.

Custom Collection Class에서 할 수 입니다.For Each할수 있습니다.

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

다음 중 하나에 의해 기능/기능 시그니처 행 바로 뒤에 표시됩니다.

  1. 클래스 모듈 내보내기, 텍스트 편집기에서 내용 편집, 다시 가져오기
  2. 러버덕 주석 사용'@Enumerator syncronizing syncronizing syncronizing syncronizing syncronizing syncronizing syncronizing.

유감스럽게도 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"과.ObjPtr(c)가 더 사용됩니다.NewEnummethod는 method의 ( 「」), 「ptrs」가 .ShowBug메서드는 값(메모리 주소)으로 작성됩니다.

말할 필요도 없이 내부 로컬 ptr 변수를 삭제합니다.ShowBug어플리케이션 크래시의 원인이 되는 것은 매우 확실합니다.

코드를 한 줄씩 밟아도 이 버그는 발생하지 않습니다!


버그 상세

버그와 .Collection되어 있습니다.CustomCollectionNew Enum enumenum능enumenumenumenumenumenumenumenum 。따라서 기본적으로 다음 중 하나를 실행해도 도움이 되지 않습니다(테스트 완료).

  1. , 「」의Optional
  2. 함수 내에서 모든 코드 제거(아래 코드 참조)
  3. IUnknownIEnumVariant
  4. FunctionProperty Get
  5. Friend ★★★★★★★★★★★★★★★★★」Static signature의
  6. DISPID_NEWENM을 Get의 Let 또는 Set추가하거나 Let/Set를 숨깁니다(즉, Let/Set를 비공개로 합니다).

2번입니다. ifCustomCollection다음과 같이 됩니다.

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
    

    컬렉션을 'Collection'으로 해도 됩니다.Set c = c
    또는 c 매개 변수 전달ByValShowBugmethod(Set)의 IUnknown 에 합니다). Ref) : 참조 추가)
    :

  3. ' ' 를 사용하다'EnumHelper'CHANGE: 'CHANGE: 'CHANGE: 'CHANGE: 'CHANGE:

    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 Each .NewEnum커스텀 컬렉션 대신 기능합니다.이것에 의해, 버그로 인한 크래시가 회피됩니다.

    함: 추가가 필요함EnumHelper. 를 추가하는 것을 잊기 쉽다..NewEnum For Each).line(실행시 오류만 트리거됨).

은 '어울릴 때(3)'가 때문입니다.c.NewEnum을 당하다ShowBug된 후 됩니다.Property Get EnumVariant내 the의 EnumHelper기본적으로 접근법 (1)은 버그를 피하는 방법입니다.


이 동작에 대한 설명은 무엇입니까?이 버그는 좀 더 우아한 방법으로 피할 수 있을까요?

편집

:CustomCollection바이발「」를 생각해 .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 Eachlinedisplaces를 합니다.

무슨 일이 일어나고 있는가?

스택 프레임은 중복되어 있는 것처럼 보이지만 중복되어서는 안 됩니다.에 충분한 변수가 있는 경우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.ptr0 ★★★★★★★★★★★★★★★★★」ptr9의 변수ShowBug실제로는 스택프레임에서 값이기 있습니다.NewEnum 메서드의 등)IEnumVariant않는 , 되고 , 「」의 프레임 ) 합니다.ShowBug「」으로서 「」NewEnum메서드는 더 커집니다(예를 들어 로컬 변수를 추가하여 크기를 늘릴 수 있습니다).상위 스택프레임과 콜스택 내의 다음 프레임 간에 공유되는 메모리가 늘어납니다.

질문에 기재된 옵션을 사용하여 버그를 회피하면 어떻게 됩니까?「」를 만으로,Set v = Nothingbefore For Each v In c syslog, "syslog: "
여기에 이미지 설명 입력

값을 모두 하면, 「」(파란색 순서)이 「」라고 하는 을 알 수.NewEnum은 return .ptr0 ★★★★★★★★★★★★★★★★★」ptr9의 변수ShowBug 회피책을되어 있는 것 .회피책을 사용하여 스택프레임이 올바르게 할당되어 있는 것 같습니다.

우리가 안에 침입하면NewEnum은 다음과
여기에 이미지 설명 입력

?For EachNewEnum

모든 VBA 클래스는 IDispatch에서 파생됩니다(이러한 클래스는 IUnknown에서 파생됩니다).

의 경우For Each...그 오브젝트의 "Loop"은 "Loop"으로 호출됩니다.IDispatch::Invoke메서드는 a를 사용하여 호출됩니다.dispIDMember4에 됩니다.에는 이미 를 VBA로 합니다.Collection에 이미 이러한 멤버가 있지만 VBA 커스텀클래스의 경우 자체 메서드를 다음과 같이 표시합니다.Attribute NewEnum.VB_UserMemId = -4발동하다

Invoke되고 있는 가 「」로 .For Each은 어원이 .IDispatchIUnknown::QueryInterface아이디스패치 경우, 「 」Invoke아이디스패치 이 이 이에요.For Each에서 ""로 선언됨"As IUnknown.ByRef또는 글로벌 또는 클래스 멤버의 커스텀 컬렉션인지 확인합니다.질문에서 언급된 회피책 1번(즉, 다른 메서드를 호출)을 사용할 뿐이지만 확인할 수 없습니다.

후크 호출

가 아닌 VB를 할 수 .Invoke스탠다드 .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://learn.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

는 '우리'를 하고 있다.Main2method 모듈합니다.

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

「」의하려면 , 더미의 합니다.IDispatch_Invoke더 커집니다(메모리 오버랩이 커집니다).

상기의 실행으로 얻을 수 있는 것은 다음과 같습니다.
여기에 이미지 설명 입력

만, 는 결코 하지 않습니다.NewEnum에 의한 Invoke 할당되었습니다.스택 프레임이 다시 잘못 할당되었습니다.

하다, 하다, 하다, 하다를 Set v = Nothingbefore 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만의 갈고리로Invoke 모두 ★★★★★★ 。DISP_E_MEMBERNOTFOUND되며, "No.NewEnum메서드가 호출됩니다.

앞에서 설명한 콜스택을 나란히 보면 다음과 같습니다.
여기에 이미지 설명 입력
VB 'VB' VB'가Invoke의 「 코드 되지 .

own ).VBA는 VBA의 Invoke의 New Enum ID를 사용합니다. (원래 IDispatch:: VB경우). NewEnum하지 않고 하면 버그가 .은 「」 「 NewEnum 」 「NewEnum 」의 경우와 .Main3 위를 하지 않습니다.For Each...VBA ★★★

버그의 원인

위의 모든 예에서 알 수 있듯이 이 버그는 다음과 같이 요약할 수 있습니다.
For Each ®IDispatch::Invoke 시시 which which which라고 부른다.NewEnum는, 「스택 포인터」의 .ShowBug(발신자 「」, 「」(「」)에 의해서 같은 됩니다.ShowBug 착신측 " " " "NewEnum를 참조해 주세요.

회피책

스택 포인터의 올바른 인크리먼트를 강제하는 방법:

  1. 을 직접For Each ( 선 ) 、Sin 1
  2. 으로 다른 For Each★★★★★
    • 의 콜IUnknown::AddRef ByVal
    • 의 콜IUnknown::QueryInterfacestdole.IUnknown
    • , 「」의Set를 하는 문AddRef ★★★★★★★★★★★★★★★★★」Release 둘 다 또둘둘((((((((((:Set c = c를 호출할 도 있습니다.QueryInterface.

질문의 '편집' 섹션에서 제안했듯이 Custom Collection 클래스를 통과할 수 있는 가능성이 항상 있는 것은 아닙니다.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는 .cls를 사용합니다.
  2. EnumHelper로 지정됩니다.이치노당신은 그것을 가질 수 있다ItemsEnum a. a. a.KeysEnum 다요. 둘 다.For Each v in c.ItemsEnum ★★★★★★★★★★★★★★★★★」For Each v in c.KeysEnum 것이다
  3. 중 .EnumHelper 호출됩니다.Invoke 멤버 ID -4 is is 、 ID - 4
  4. 당신은 더 이상 사고를 당하지 않을 것입니다.""로 For Each v in c.NewEnum 그 and를 사용합니다.For Each v in c런타임 오류만 발생하며 테스트에서 확인될 수 있습니다.까지는 '크래시'의 시켜 강제 크래시를 수.c.NewEnum 방법으로ByRef 음음음음음 a a a a a a a a a a a a a a a a a a a a a a a a a a a a를 해야 합니다.For Each 또는 다른 메서드 호출 Set 리는 없겠지
  5. 언급할 가 있는 것은 한다는 것입니다.EnumHelper 내의 의 클래스

repair가 부족해서 코멘트를 추가할 수도 없고, frozen이라 채팅창을 사용할 수도 없습니다만, 여기서 제시한 솔루션 중 어느 것도 테스트하지 않았지만, 같은 버그인 것 같습니다.

여기서 설명하려고 했습니다.

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

테스트에 의해서도 문제가 해결되기를 희망하고 있습니다.그렇다면 문제를 조사하고 64비트 VBA에 코드를 이식할 수 없었던 것에 대한 회피책을 제공해 주셔서 진심으로 감사드립니다.

언급URL : https://stackoverflow.com/questions/63848617/bug-with-for-each-enumeration-on-x64-custom-classes

반응형