Collection Extender


Author: Dave
Date: 08.31.16 - 11:34pm



I love the vb6 collection object. I frequently use it to hold object arrays and cycle through them with for each. Its also nice to have a .count = 0 to see if its empty instead of having to include an AryIsEmpty(ary) function to see if its been initialized yet.

One weak spot of collections is its key mechanism. It kind of sucks in a couple ways. First, it really needs a built in keyExists method, since trying to access an nonexistant key, or trying to add an existing key both raise errors. an ensureUniqueKey(suggested) is also handy.

One other weakness is, there is no way to tell what an item key is once its set. I mean usually you know, or have it stored somewhere else..but what if you had to search a collection for a value and find it..which key was this? I have had a need to translate the two back and forth before.

One more weakness is that you can not use a numeric key, or even a key which starts with a number. I need this fairly often wanting to add keys for things such as window handles, object pointers, database id etc.

So over at vbforums.com this week, some guys have been talking about collections and the internal structures. Elroys post on a Wrapper for VB6 Collections really caught my interest.

Using these internal structures, they figured out a way to enum all the Keys for the objects, get KeyForIndex, ChangeKey, ChangeIndex etc. Very cool stuff.

Many moons ago i wrote my own collection wrapper, but I dont think it ever survived outside of the one or two projects I used it in. If i remember correctly, I couldnt replicate the for each support that I love so much without adding the extra step of making the underlying collection public in the class. I also tried tracking all the keys in a parallel array to have an enumkeys methods or key for index method. It was bulky and not quite stable and what if an item is removed. Not pretty. This is a better way to do it.

One approach I ended up using in these situations was to always use a small class wrapper for data with my own name, key, value elements. I would add them to the vb collection without a key, then access them through my own lookup methods. Works and is stable, but you end up writing lots of stub classes and search methods.

After playing with the class for a while and digesting what was there, I think I have boiled it down to a final incarnation I want to use.

Since collections get used ALLOT, i want to keep the wrapper itself down to a minimum. The extended methods which are cool to have, but not a daily driver will be a separate reusable class that can be loaded on demand.

Dim extras As CCollectionExtender

'initilize the class on demand (memory saver)
Property Get ext() As CCollectionExtender
    If extras Is Nothing Then
        Set extras = New CCollectionExtender
        extras.setTarget c
    End If
    Set ext = extras
End Property


My final layout looks like this.

CollectionEx:
Add, Clear, fromFile, toFile, Item (default), KeyExists, keyForIndex, Remove, toString, uniqueKey, ext (as above), Count, isEmpty


CCollectionExtender:
changeIndex, changeIndexByKey, changeKey, changeKeyByIndex, fromArray, fromFile, indexForKey, KeyExists, keyforIndex, Keys, toArray, toFile, uniqueKey, isEmpty, toString


There is some duplication between the two, thats because the CCollectionExtender can also be used on its own with a regular collection using a setTarget method. No need to tie it so tightly to CollectionEx that it cant stand on its own.

I havent used it in a real project yet, but I think its a keeper.

Benifits:
  • I really like having Count as a property so you can mouse over it in the IDE and see the value,
  • keyForIndex will solve a real problem I have had intermittently for years,
  • KeyExists will be built in,
  • isEmpty while small is highly readable,
  • toString will be super usable for debugging or an interm to toArray,
  • toFile/fromFile will be create to both save a collection to disk and reload it, as well as providing an easy basis for a clone method to make a full copy (including keys) and an easy way to append one collection onto the other (including keys).


Lots of really cool stuff! Big thanks goes out to Elroy and all the authors involved in discovering these structures.

Addemdum:

After really going through all the methods and digesting what was done, it turns out you only need one extra primitive that mucks with CopyMemory and the internal collection structures.

All the rest of the methods such as enum keys can be accomplished with just the keyFromIndex(index as long).

This methods takes a vb6 collection, and return the key associated with the element at a given index.

Private c as Collection

Public Function keyForIndex(index As Long) As String
    ' Get a key based on its index value.  Must be in range, or error.
    Dim i     As Long
    Dim ptr   As Long
    Dim sKey  As String
    '
    If index < 1 Or index > c.Count Then
        Err.Raise 9
        Exit Function
    End If
    '
    If index <= c.Count / 2 Then                                ' Start from front.
        CopyMemory ptr, ByVal ObjPtr(c) + &H18, 4               ' First item pointer of collection header.
        For i = 2 To index
            CopyMemory ptr, ByVal ptr + &H18, 4                 ' Next item pointer of collection item.
        Next i
    Else                                                        ' Start from end and go back.
        CopyMemory ptr, ByVal ObjPtr(c) + &H1C, 4               ' Last item pointer of collection header.
        For i = c.Count - 1 To index Step -1
            CopyMemory ptr, ByVal ptr + &H14, 4                 ' Previous item pointer of collection item.
        Next i
    End If
    '
    i = StrPtr(sKey)                                            ' Save string pointer because we're going to borrow the string.
    CopyMemory ByVal VarPtr(sKey), ByVal ptr + &H10, 4          ' Key string of collection item.
    keyForIndex = Base16Decode(sKey)                                ' Move key into property's return.
    CopyMemory ByVal VarPtr(sKey), i, 4                         ' Put string pointer back to keep memory straight.
End Function





Comments: (1)

On 09.01.16 - 12:10pm Dave wrote:
One more note. I had to look at the look at the code above a bit before it made sense what it was doing. It looks like it has to walk a linked list with flink and blink members.

The reason for If index <= c.Count / 2 Then is a matter of minimizing the number of structures you have to walk through. If its in the first half of the list, walk the flink members until you hit that index. If its in the last half of the collection, start at the end and walk backward.

Its a smart optimization. A mention of flink and blink should bring it into focus..

 
Leave Comment:
Name:
Email: (not shown)
Message: (Required)
Math Question: 78 + 67 = ? followed by the letter: C 



About Me
More Blogs
Main Site
Posts: (All)
2020 ( 2 )
2019 ( 5 )
2018 ( 6 )
2017 ( 6 )
2016 (22)
     VB6 CDECL
     UDT Tricks pt2
     Remote Data Extraction
     Collection Extender
     VB6 FindResource
     CDO.Message
     DirList Single Click
     Reset CheckPoint VPN Policy
     VB6 BSTR Oddities Explained
     SafeArrays in C
     BSTR and Variant in C++
     Property let optional args
     Misc Libs
     Enum Named Pipes
     Vb6 Collection in C++
     VB6 Overloaded Methods
     EXPORT FUNCDNAME Warning
     VB6 Syncronous Socket
     Simple IPC
     VB6 Auto Resize Form Elements
     Mach3 Automation
     Exit For in While
2015 ( 16 )
2014 ( 25 )
2013 ( 4 )
2012 ( 10 )
2011 ( 7 )
2010 ( 11 )
2009 ( 3 )