Results 1 to 22 of 22

Thread: [VB6] IEnumVARIANT / For Each support without a typelib

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,224

    [VB6] IEnumVARIANT / For Each support without a typelib

    In my own projects I use a typelib and a custom interface to do the same thing, (comparable to .NET and Olaf's examples) which might seem overly complex, so here's an example that gets the job done without any dependencies. It also serves as a good example of creating a Lightweight COM Object that's less complex than Curland's examples (which are always over-complicated). It should be easy enough to adapt to your own custom collections.

    Code:
    '
    ' MEnumerator.bas
    '
    ' Implementation of IEnumVARIANT to support For Each in VB6
    '
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Option Explicit
    
    Private Type TENUMERATOR
        VTablePtr   As Long
        References  As Long
        Enumerable  As Object
        Index       As Long
        Upper       As Long
        Lower       As Long
    End Type
    
    Private Enum API
        NULL_ = 0
        S_OK = 0
        S_FALSE = 1
        E_NOTIMPL = &H80004001
        E_NOINTERFACE = &H80004002
        E_POINTER = &H80004003
    #If False Then
        Dim NULL_, S_OK, S_FALSE, E_NOTIMPL, E_NOINTERFACE, E_POINTER
    #End If
    End Enum
    
    Private Declare Function FncPtr Lib "msvbvm60" Alias "VarPtr" (ByVal Address As Long) As Long
    Private Declare Function GetMem4 Lib "msvbvm60" (Src As Any, Dst As Any) As Long
    Private Declare Function CopyBytesZero Lib "msvbvm60" Alias "__vbaCopyBytesZero" (ByVal Length As Long, Dst As Any, Src As Any) As Long
    Private Declare Function CoTaskMemAlloc Lib "ole32" (ByVal cb As Long) As Long
    Private Declare Sub CoTaskMemFree Lib "ole32" (ByVal pv As Long)
    Private Declare Function IIDFromString Lib "ole32" (ByVal lpsz As Long, ByVal lpiid As Long) As Long
    Private Declare Function SysAllocStringByteLen Lib "oleaut32" (ByVal psz As Long, ByVal cblen As Long) As Long
    Private Declare Function VariantCopyToPtr Lib "oleaut32" Alias "VariantCopy" (ByVal pvargDest As Long, ByRef pvargSrc As Variant) As Long
    Private Declare Function InterlockedIncrement Lib "kernel32" (ByRef Addend As Long) As Long
    Private Declare Function InterlockedDecrement Lib "kernel32" (ByRef Addend As Long) As Long
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Public Function NewEnumerator(ByRef Enumerable As Object, _
                                  ByVal Upper As Long, _
                                  Optional ByVal Lower As Long _
                                  ) As IEnumVARIANT
    ' Class Factory
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    
        Static VTable(6) As Long
        If VTable(0) = NULL_ Then
            ' Setup the COM object's virtual table
            VTable(0) = FncPtr(AddressOf IUnknown_QueryInterface)
            VTable(1) = FncPtr(AddressOf IUnknown_AddRef)
            VTable(2) = FncPtr(AddressOf IUnknown_Release)
            VTable(3) = FncPtr(AddressOf IEnumVARIANT_Next)
            VTable(4) = FncPtr(AddressOf IEnumVARIANT_Skip)
            VTable(5) = FncPtr(AddressOf IEnumVARIANT_Reset)
            VTable(6) = FncPtr(AddressOf IEnumVARIANT_Clone)
        End If
        
        Dim This As TENUMERATOR
        With This
            ' Setup the COM object
            .VTablePtr = VarPtr(VTable(0))
            .References = 1
            Set .Enumerable = Enumerable
            .Lower = Lower
            .Index = Lower
            .Upper = Upper
        End With
        
        ' Allocate a spot for it on the heap
        Dim pThis As Long
        pThis = CoTaskMemAlloc(LenB(This))
        If pThis Then
            ' CopyBytesZero is used to zero out the original
            ' .Enumerable reference, so that VB doesn't mess up the
            ' reference count, and free our enumerator out from under us
            CopyBytesZero LenB(This), ByVal pThis, This
            DeRef(VarPtr(NewEnumerator)) = pThis
        End If
    End Function
    
    Private Function RefToIID$(ByVal riid As Long)
        ' copies an IID referenced into a binary string
        Const IID_CB As Long = 16&  ' GUID/IID size in bytes
        DeRef(VarPtr(RefToIID)) = SysAllocStringByteLen(riid, IID_CB)
    End Function
    
    Private Function StrToIID$(ByRef iid As String)
        ' converts a string to an IID
        StrToIID = RefToIID$(NULL_)
        IIDFromString StrPtr(iid), StrPtr(StrToIID)
    End Function
    
    Private Function IID_IUnknown() As String
        Static iid As String
        If StrPtr(iid) = NULL_ Then _
            iid = StrToIID$("{00000000-0000-0000-C000-000000000046}")
        IID_IUnknown = iid
    End Function
    
    Private Function IID_IEnumVARIANT() As String
        Static iid As String
        If StrPtr(iid) = NULL_ Then _
            iid = StrToIID$("{00020404-0000-0000-C000-000000000046}")
        IID_IEnumVARIANT = iid
    End Function
    
    Private Function IUnknown_QueryInterface(ByRef This As TENUMERATOR, _
                                             ByVal riid As Long, _
                                             ByVal ppvObject As Long _
                                             ) As Long
        If ppvObject = NULL_ Then
            IUnknown_QueryInterface = E_POINTER
            Exit Function
        End If
    
        Select Case RefToIID$(riid)
            Case IID_IUnknown, IID_IEnumVARIANT
                DeRef(ppvObject) = VarPtr(This)
                IUnknown_AddRef This
                IUnknown_QueryInterface = S_OK
            Case Else
                IUnknown_QueryInterface = E_NOINTERFACE
        End Select
    End Function
    
    Private Function IUnknown_AddRef(ByRef This As TENUMERATOR) As Long
        IUnknown_AddRef = InterlockedIncrement(This.References)
    End Function
    
    Private Function IUnknown_Release(ByRef This As TENUMERATOR) As Long
        IUnknown_Release = InterlockedDecrement(This.References)
        If IUnknown_Release = 0& Then
            Set This.Enumerable = Nothing
            CoTaskMemFree VarPtr(This)
        End If
    End Function
    
    Private Function IEnumVARIANT_Next(ByRef This As TENUMERATOR, _
                                       ByVal celt As Long, _
                                       ByVal rgVar As Long, _
                                       ByRef pceltFetched As Long _
                                       ) As Long
        
        Const VARIANT_CB As Long = 16 ' VARIANT size in bytes
        
        If rgVar = NULL_ Then
            IEnumVARIANT_Next = E_POINTER
            Exit Function
        End If
        
        Dim Fetched As Long
        With This
            Do Until .Index > .Upper
                VariantCopyToPtr rgVar, .Enumerable(.Index)
                .Index = .Index + 1&
                Fetched = Fetched + 1&
                If Fetched = celt Then Exit Do
                rgVar = PtrAdd(rgVar, VARIANT_CB)
            Loop
        End With
        
        If VarPtr(pceltFetched) Then pceltFetched = Fetched
        If Fetched < celt Then IEnumVARIANT_Next = S_FALSE
    End Function
    
    Private Function IEnumVARIANT_Skip(ByRef This As TENUMERATOR, ByVal celt As Long) As Long
        IEnumVARIANT_Skip = E_NOTIMPL
    End Function
    
    Private Function IEnumVARIANT_Reset(ByRef This As TENUMERATOR) As Long
        IEnumVARIANT_Reset = E_NOTIMPL
    End Function
    
    Private Function IEnumVARIANT_Clone(ByRef This As TENUMERATOR, ByVal ppEnum As Long) As Long
        IEnumVARIANT_Clone = E_NOTIMPL
    End Function
    
    Private Function PtrAdd(ByVal Pointer As Long, ByVal Offset As Long) As Long
        Const SIGN_BIT As Long = &H80000000
        PtrAdd = (Pointer Xor SIGN_BIT) + Offset Xor SIGN_BIT
    End Function
    
    Private Property Let DeRef(ByVal Address As Long, ByVal Value As Long)
        GetMem4 Value, ByVal Address
    End Property
    Attached Files Attached Files
    Last edited by DEXWERX; Jul 20th, 2018 at 02:32 PM.

  2. #2
    Lively Member
    Join Date
    Jul 2015
    Location
    Poland (moved away from Belarus)
    Posts
    110

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Can I use this implementation in FTypes project?

  3. #3
    Lively Member
    Join Date
    Sep 2016
    Location
    Texas panhandle
    Posts
    64

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Can it be modified to handle strings?

  4. #4
    Lively Member
    Join Date
    Jul 2015
    Location
    Poland (moved away from Belarus)
    Posts
    110

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Quote Originally Posted by VBClassic04 View Post
    Can it be modified to handle strings?
    Sorry, i'm not an author of this thread, but what do you mean of "handle strings"? If your code is about to enumerate string characters - than of course it can be used.

  5. #5

    Thread Starter
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,224

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Quote Originally Posted by VBClassic04 View Post
    Can it be modified to handle strings?
    No modification necessary.
    Sure, I'll post a modified example if you want.
    Attached Files Attached Files
    Last edited by DEXWERX; Nov 13th, 2017 at 12:39 PM.

  6. #6

    Thread Starter
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,224

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Quote Originally Posted by hwoarang View Post
    Can I use this implementation in FTypes project?
    Of course. Use it how you see fit.

  7. #7
    Lively Member
    Join Date
    Jul 2015
    Location
    Poland (moved away from Belarus)
    Posts
    110

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Hi DEXWERX.

    I found a crash of VB IDE when trying to put breakpoint on Print I line in form. Then drag your V variable to watch window and press Stop (end program execution).
    Last edited by hwoarang; Nov 18th, 2017 at 02:02 AM.

  8. #8
    New Member
    Join Date
    Nov 2017
    Posts
    10

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Halting execution in the IDE with the stop button doesn't give things a chance to tear-down properly. The enumerator is completely managed within the program and not by VB. It requires a call to the reference release method in the MEnumerator module. That module is most likely already removed from memory, so if the enumerator is attempting to release itself it doesn't have a valid memory location to call. Or if the enumerator just ends up being leaked memory maybe the IDE fails with that. Either way it's not a good outcome.

  9. #9
    Lively Member
    Join Date
    Jul 2015
    Location
    Poland (moved away from Belarus)
    Posts
    110

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    That crash happens only when ending up a program before last item is enumerated. I assume that memory is not valid when releasing (did no research yet). Anyway - I do not think that it is critical case for now, since enumeration itself works perfectly. Possible crash is a rare case when you stop execution in the middle of process.

  10. #10

    Thread Starter
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,224

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Quote Originally Posted by hwoarang View Post
    Hi DEXWERX.

    I found a crash of VB IDE when trying to put breakpoint on Print I line in form. Then drag your V variable to watch window and press Stop (end program execution).
    As killian pointed out, it's definitely a teardown issue, similar to subclassing. If I have time later this week I will try and track down the exact point it crashes. Even when you hit stop - the IDE seems to allow code in static modules to continue executing (even after being reset), so there may be an easy fix.

  11. #11
    Lively Member
    Join Date
    Jul 2015
    Location
    Poland (moved away from Belarus)
    Posts
    110

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Quote Originally Posted by DEXWERX View Post
    As killian pointed out, it's definitely a teardown issue, similar to subclassing. If I have time later this week I will try and track down the exact point it crashes. Even when you hit stop - the IDE seems to allow code in static modules to continue executing (even after being reset), so there may be an easy fix.
    Hopefully I noticed that by accident when debuging FTypes project. But first issue there was that IEnumVARIANT_Skip, IEnumVARIANT_Reset and IEnumVARIANT_Clone were not included into methods table (so no reference exist) like from your sample of minimal implementation. Including that methods with return of not implemented flag allows IDE Watch feature to work, otherwise - crash right after you added a variable and try to look at content.

    So next issue (as i already noticed - not critical) - is stop inside of foreach loop.

  12. #12

    Thread Starter
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,224

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Quote Originally Posted by hwoarang View Post
    Hopefully I noticed that by accident when debuging FTypes project. But first issue there was that IEnumVARIANT_Skip, IEnumVARIANT_Reset and IEnumVARIANT_Clone were not included into methods table (so no reference exist) like from your sample of minimal implementation. Including that methods with return of not implemented flag allows IDE Watch feature to work, otherwise - crash right after you added a variable and try to look at content.

    So next issue (as i already noticed - not critical) - is stop inside of foreach loop.
    ah good info! the minimal implementation took a few shortcuts too many, to be viable for the IDE.

  13. #13

    Thread Starter
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,224

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    A slightly cleaned up version, haven't debugged this yet.

    Code:
    ' Copyright © 2018 Dexter Freivald. All Rights Reserved. DEXWERX.COM
    '
    ' MEnumerator.bas
    '
    ' Implementation of IEnumVARIANT to support For Each in VB6
    '
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Option Explicit
    
    Private Type TENUMERATOR
        VTablePtr   As Long
        References  As Long
        Enumerable  As Object
        Index       As Long
        Upper       As Long
        Lower       As Long
    End Type
    
    Private Enum API
        NULL_ = 0
        S_OK = 0
        S_FALSE = 1
        E_NOTIMPL = &H80004001
        E_NOINTERFACE = &H80004002
        E_POINTER = &H80004003
    #If False Then
        Dim NULL_, S_OK, S_FALSE, E_NOTIMPL, E_NOINTERFACE, E_POINTER
    #End If
    End Enum
    
    Private Declare Function FncPtr Lib "msvbvm60" Alias "VarPtr" (ByVal Address As Long) As Long
    Private Declare Function GetMem4 Lib "msvbvm60" (Src As Any, Dst As Any) As Long
    Private Declare Function CopyBytesZero Lib "msvbvm60" Alias "__vbaCopyBytesZero" (ByVal Length As Long, Dst As Any, Src As Any) As Long
    Private Declare Function CoTaskMemAlloc Lib "ole32" (ByVal cb As Long) As Long
    Private Declare Sub CoTaskMemFree Lib "ole32" (ByVal pv As Long)
    Private Declare Function IIDFromString Lib "ole32" (ByVal lpsz As Long, ByVal lpiid As Long) As Long
    Private Declare Function SysAllocStringByteLen Lib "oleaut32" (ByVal psz As Long, ByVal cblen As Long) As Long
    Private Declare Function VariantCopyToPtr Lib "oleaut32" Alias "VariantCopy" (ByVal pvargDest As Long, ByRef pvargSrc As Variant) As Long
    Private Declare Function InterlockedIncrement Lib "kernel32" (ByRef Addend As Long) As Long
    Private Declare Function InterlockedDecrement Lib "kernel32" (ByRef Addend As Long) As Long
    
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    Public Function NewEnumerator(ByRef Enumerable As Object, _
                                  ByVal Upper As Long, _
                                  Optional ByVal Lower As Long _
                                  ) As IEnumVARIANT
    ' Class Factory
    ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
    
        Static VTable(6) As Long
        If VTable(0) = NULL_ Then
            ' Setup the COM object's virtual table
            VTable(0) = FncPtr(AddressOf IUnknown_QueryInterface)
            VTable(1) = FncPtr(AddressOf IUnknown_AddRef)
            VTable(2) = FncPtr(AddressOf IUnknown_Release)
            VTable(3) = FncPtr(AddressOf IEnumVARIANT_Next)
            VTable(4) = FncPtr(AddressOf IEnumVARIANT_Skip)
            VTable(5) = FncPtr(AddressOf IEnumVARIANT_Reset)
            VTable(6) = FncPtr(AddressOf IEnumVARIANT_Clone)
        End If
        
        Dim This As TENUMERATOR
        With This
            ' Setup the COM object
            .VTablePtr = VarPtr(VTable(0))
            .References = 1
            Set .Enumerable = Enumerable
            .Lower = Lower
            .Index = Lower
            .Upper = Upper
        End With
        
        ' Allocate a spot for it on the heap
        Dim pThis As Long
        pThis = CoTaskMemAlloc(LenB(This))
        If pThis Then
            ' CopyBytesZero is used to zero out the original
            ' .Enumerable reference, so that VB doesn't mess up the
            ' reference count, and free our enumerator out from under us
            CopyBytesZero LenB(This), ByVal pThis, This
            DeRef(VarPtr(NewEnumerator)) = pThis
        End If
    End Function
    
    Private Function RefToIID$(ByVal riid As Long)
        ' copies an IID referenced into a binary string
        Const IID_CB As Long = 16&  ' GUID/IID size in bytes
        DeRef(VarPtr(RefToIID)) = SysAllocStringByteLen(riid, IID_CB)
    End Function
    
    Private Function StrToIID$(ByRef iid As String)
        ' converts a string to an IID
        StrToIID = RefToIID$(NULL_)
        IIDFromString StrPtr(iid), StrPtr(StrToIID)
    End Function
    
    Private Function IID_IUnknown() As String
        Static iid As String
        If StrPtr(iid) = NULL_ Then _
            iid = StrToIID$("{00000000-0000-0000-C000-000000000046}")
        IID_IUnknown = iid
    End Function
    
    Private Function IID_IEnumVARIANT() As String
        Static iid As String
        If StrPtr(iid) = NULL_ Then _
            iid = StrToIID$("{00020404-0000-0000-C000-000000000046}")
        IID_IEnumVARIANT = iid
    End Function
    
    Private Function IUnknown_QueryInterface(ByRef This As TENUMERATOR, _
                                             ByVal riid As Long, _
                                             ByVal ppvObject As Long _
                                             ) As Long
        If ppvObject = NULL_ Then
            IUnknown_QueryInterface = E_POINTER
            Exit Function
        End If
    
        Select Case RefToIID$(riid)
            Case IID_IUnknown, IID_IEnumVARIANT
                DeRef(ppvObject) = VarPtr(This)
                IUnknown_AddRef This
                IUnknown_QueryInterface = S_OK
            Case Else
                IUnknown_QueryInterface = E_NOINTERFACE
        End Select
    End Function
    
    Private Function IUnknown_AddRef(ByRef This As TENUMERATOR) As Long
        IUnknown_AddRef = InterlockedIncrement(This.References)
    End Function
    
    Private Function IUnknown_Release(ByRef This As TENUMERATOR) As Long
        IUnknown_Release = InterlockedDecrement(This.References)
        If IUnknown_Release = 0& Then
            Set This.Enumerable = Nothing
            CoTaskMemFree VarPtr(This)
        End If
    End Function
    
    Private Function IEnumVARIANT_Next(ByRef This As TENUMERATOR, _
                                       ByVal celt As Long, _
                                       ByVal rgVar As Long, _
                                       ByRef pceltFetched As Long _
                                       ) As Long
        
        Const VARIANT_CB As Long = 16 ' VARIANT size in bytes
        
        If rgVar = NULL_ Then
            IEnumVARIANT_Next = E_POINTER
            Exit Function
        End If
        
        Dim Fetched As Long
        With This
            Do Until .Index > .Upper
                VariantCopyToPtr rgVar, .Enumerable(.Index)
                .Index = .Index + 1&
                Fetched = Fetched + 1&
                If Fetched = celt Then Exit Do
                rgVar = PtrAdd(rgVar, VARIANT_CB)
            Loop
        End With
        
        If VarPtr(pceltFetched) Then pceltFetched = Fetched
        If Fetched < celt Then IEnumVARIANT_Next = S_FALSE
    End Function
    
    Private Function IEnumVARIANT_Skip(ByRef This As TENUMERATOR, ByVal celt As Long) As Long
        IEnumVARIANT_Skip = E_NOTIMPL
    End Function
    
    Private Function IEnumVARIANT_Reset(ByRef This As TENUMERATOR) As Long
        IEnumVARIANT_Reset = E_NOTIMPL
    End Function
    
    Private Function IEnumVARIANT_Clone(ByRef This As TENUMERATOR, ByVal ppEnum As Long) As Long
        IEnumVARIANT_Clone = E_NOTIMPL
    End Function
    
    Private Function PtrAdd(ByVal Pointer As Long, ByVal Offset As Long) As Long
        Const SIGN_BIT As Long = &H80000000
        PtrAdd = (Pointer Xor SIGN_BIT) + Offset Xor SIGN_BIT
    End Function
    
    Private Property Let DeRef(ByVal Address As Long, ByVal Value As Long)
        GetMem4 Value, ByVal Address
    End Property
    Last edited by DEXWERX; Jan 5th, 2018 at 04:22 PM.

  14. #14
    Lively Member
    Join Date
    Jul 2015
    Location
    Poland (moved away from Belarus)
    Posts
    110

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Hi, DEXWERX.

    I found a bug and fixed it. The issue is in IEnumVARIANT_Next method. And this issues is referenced to a case when you loop some collection and do remove an element inside iteration.

    Code:
    Private Function IEnumVARIANT_Next(ByRef This As TENUMERATOR, ByVal lCelt As Long, ByVal lVar As Long, ByVal lFetched As Long) As Long
        
        '------------------------------------------------------------------------------------------------------------------------------------------'
        '
        ' NOTES: Requires enumerable class to have Count and Item properties.
        '
        '------------------------------------------------------------------------------------------------------------------------------------------'
        
        Dim c As Long
        
        With This
            
            c = .uEnumerable.Count - 1&
            
            If .lIndex > .lUpper Then
                
                IEnumVARIANT_Next = 1&
            
            Else
                
                If c < .lUpper Then
                    
                    .lIndex = .lIndex - 1&
                    .lUpper = c
                
                End If
                
                VariantCopy lVar, .uEnumerable.Item(.lIndex)
                
                .lIndex = .lIndex + 1&
            
            End If
        
        End With
    
    End Function
    You can see that new version compares initial Count passed in lUpper structure member with actual Count. If second is less - we decrement lIndex and update lUpper to correctly proceed next iteration (and exit loop if needed).

    Of course uEnumerable is now mandatory to have Count property. I do not think it is a problem.

    I also have in mind a case when somebody removes several items at once inside single iteration ...

    All above seems to be true only for lightweight version

    Regards
    Last edited by hwoarang; Apr 24th, 2018 at 12:44 PM.

  15. #15

    Thread Starter
    PowerPoster
    Join Date
    Jun 2015
    Posts
    2,224

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Quote Originally Posted by hwoarang View Post
    Hi, DEXWERX.

    I found a bug and fixed it. The issue is in IEnumVARIANT_Next method. And this issues is referenced to a case when you loop some collection and do remove an element inside iteration.

    You can see that new version compares initial Count passed in lUpper structure member with actual Count. If second is less - we decrement lIndex and update lUpper to correctly proceed next iteration (and exit loop if needed).

    Of course uEnumerable is now mandatory to have Count property. I do not think it is a problem.

    I also have in mind a case when somebody removes several items at once inside single iteration ...

    All above seems to be true only for lightweight version

    Regards
    Cool but I wouldn't call it a bug. This is normal for enumerators (any language/platform)
    Removing/adding during enumeration is undefined behavior, and should never be done.
    The best thing would be to throw an exception if iterating to the next item after a list is modified.
    It makes sense when you realize enumeration can be used over any list type.

    Typically you can clone a list, and enumerate over the clone if this is desired, but still avoiding iterating over a modified list.

    references:
    https://msdn.microsoft.com/en-us/lib...v=vs.110).aspx
    Quote Originally Posted by MSDN
    An enumerator remains valid as long as the collection remains unchanged. If changes are made to the collection, such as adding, modifying, or deleting elements, the enumerator is irrecoverably invalidated and the next call to MoveNext or Reset throws an InvalidOperationException.
    https://docs.oracle.com/javase/7/doc...Exception.html
    Last edited by DEXWERX; Apr 24th, 2018 at 02:36 PM.

  16. #16
    Lively Member
    Join Date
    Jul 2015
    Location
    Poland (moved away from Belarus)
    Posts
    110

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Yes, I'm aware of that. The reason of standard iterators do not recheck bounds actually is thread safety.

    I do not think that modifying a collection inside a loop should never be done because sometimes it is pretty painfull to determine item(s) to be removed.

  17. #17
    Hyperactive Member
    Join Date
    Jan 2015
    Posts
    323

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    what it the difference between skills this thread indicate and the below
    Code:
    Public Property Get NewEnum() As IUnknown
    Attribute NewEnum.VB_UserMemId = -4
    Attribute NewEnum.VB_MemberFlags = "40"
        Set NewEnum = mCol.[_NewEnum]
    End Property

  18. #18
    Lively Member
    Join Date
    Jul 2015
    Location
    Poland (moved away from Belarus)
    Posts
    110

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Quote Originally Posted by loquat View Post
    what it the difference between skills this thread indicate and the below
    Code:
    Public Property Get NewEnum() As IUnknown
    Attribute NewEnum.VB_UserMemId = -4
    Attribute NewEnum.VB_MemberFlags = "40"
        Set NewEnum = mCol.[_NewEnum]
    End Property
    The difference is that in your sample you need to declare a private Collection variable inside your custom class to use enumeration supported by this collection out of the box. Thus you need to store all your elements inside this collection for futher enumerating. Sometimes this can be pretty hard overload that affects performance and maintanability.

    DEXWERX opposite provides an implementation of Enumerable interface that can be used by your custom class (that is supposed to support enumeration) without embedded Collection variable.
    Last edited by hwoarang; May 1st, 2018 at 11:03 AM.

  19. #19
    Fanatic Member
    Join Date
    Nov 2013
    Posts
    658

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    @DEXWERX

    Code:
    Private Type TENUMERATOR
        VTablePtr   As Long
        References  As Long
        Enumerable  As Object
        Index       As Long
        Upper       As Long
        Lower       As Long
    End Type
    Where did you find info about the ENUMERATOR Object ? I have looked in the MS documentation for the IEnumVARIANT Interface but I couldn't find any info about the enumerator object.

    Thanks for the great code.

  20. #20
    PowerPoster
    Join Date
    Jun 2012
    Posts
    2,393

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Quote Originally Posted by JAAFAR View Post
    @DEXWERX

    Code:
    Private Type TENUMERATOR
        VTablePtr   As Long
        References  As Long
        Enumerable  As Object
        Index       As Long
        Upper       As Long
        Lower       As Long
    End Type
    Where did you find info about the ENUMERATOR Object ? I have looked in the MS documentation for the IEnumVARIANT Interface but I couldn't find any info about the enumerator object.

    Thanks for the great code.
    That's the layout of the internal variables the object has. It's not restricted. Though it is recommended to have the RefCount variable right after VTablePtr. Seems like a common thing in VBx objects. However, it's not a rule or so.

  21. #21
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    5,163

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    Quote Originally Posted by JAAFAR View Post
    @DEXWERX

    Code:
    Private Type TENUMERATOR
        VTablePtr   As Long
        References  As Long
        Enumerable  As Object
        Index       As Long
        Upper       As Long
        Lower       As Long
    End Type
    Where did you find info about the ENUMERATOR Object ? I have looked in the MS documentation for the IEnumVARIANT Interface but I couldn't find any info about the enumerator object.
    This struct is entirely user-defined internal object state with only requirement for the first member to be a pointer to interface VTable to be considered (a pointer to) a valid COM interface.

    This is exactly like having

    Private m_lReferenced As Long
    Private m_oEnumberable As Object
    Private m_lIndex As Long

    . . . and so on private variables inside an ordinary VB6 class.

    These member variables consitute the internal object state, the order of variables does not matter for clients, the type of the variable too, its all an implementation detail that remains encapsulated/hidden from outside.

    Struct TENUMERATOR does *not* match any system-supplied structure, it's an implementation detail that OP of this thread decided to use as it's most convenient for his original pupose -- implementing an IEnumVARIANT forwarding proxy.

    cheers,
    </wqw>

  22. #22
    Fanatic Member
    Join Date
    Nov 2013
    Posts
    658

    Re: [VB6] IEnumVARIANT / For Each support without a typelib

    @Krool and @wqweto
    Lovely explanation
    Thanks.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width