Code Samples, Tips, Tricks and
other Neat Stuff for the VB developer

All Content Copyright © 2000
New Vision Software
All rights reserved

VB Petition

Many of you may not be aware that Microsoft's mainstream support for VB6 officially ended on March 31st, 2005.  Now that this has occurred, it becomes increasingly more likely that future updates to Windows may break existing VB code.  If you are concerned about this and/or are concerned about migration issues associated with bringing existing VB code to the .NET platform, please take a moment to read and sign the petition asking Microsoft to extend there COM based VB product line.  The impetus behind the petition is the desire to give companies and individuals with substantial investments in existing VB code a path forward to the latest development platform without the current requirement for wholesale rewrites of existing code.  Thanks for your support!

Bryan

   

Home

All Sample Projects

Grouped Samples

Common Dialogs

Control Stuff

Date & Timezone

Drawing

Graphics

Text

File Routines

Form Modifications

Icon Routines

Listbox & ComboBox

Menus

Mouse

Subclassing

Text Parsing Routines

UserControl Demos

Specific Samples

Activate Previous

Instance

API & OLE DragDrop

Environment Variables

GUID - UUID

ListView

Message Box

Collections

Thunking

File Split Utility

Save Clipboard Utility

Visual Basic Tips!


 

Create Your Own "Super Collections" in VB

by Bryan Stafford - New Vision Software

Have you ever wanted a collection that would allow you to see if a particular key exists without having to trap an error?  How about  the ability to rename a key without having to first remove the item and then add it back to the collection with the new key?  Maybe being able to get the key for a particular item by passing the index would be nice?  Or, have you ever needed to be able to use case sensitive key names?  What about a collection that starts with an index other than one?  These are but a few of the areas where VB’s collection object falls short.

I’m sure you are saying to yourself that all of these things are already possible if you write your own collection class.   However, you then loose the ability to iterate the collection using the For Each…Next syntax unless, of course, you wrap the VB collection object and delegate the method for your class to the _NewEnum method of the VB collection.  Unfortunately, this gets you right back where you started, trying to fix the shortcomings of the VB collection but having to use the VB collection to do it.

In this article I will show you a relatively easily method for implementing the IEnumVARIANT interface in your VB classes.  This allows you to give any VB class the ability to use the For Each…Next syntax for iterating through items without having to delegate to VB’s collection.  Once you are able to ditch VB’s collection object, the sky is the limit when it comes to the amount of  power and flexibility you want to offer in your collections. 

First, we need to talk about exactly what goes on under the hood when you use a For Each…Next loop to enumerate the objects in a collection.  For a collection to work with the For Each...Next syntax, it must have a NewEnum method on it's interface.  You can specify a NewEnum method in any VB class by creating a public property with the name "NewEnum" and setting it's procedure ID to (-4) via the procedure attributes dialog in the VB IDE.  When your code hits the For Each line in the loop structure, VB calls the NewEnum method of the collection object.  This method must return a pointer to an IEnumVARIANT interface.  The IEnumVARIANT interface is usually implemented in a separate object created specifically for the enumeration.  Once the behind the scenes looping structure has a pointer to the interface, it repeatedly calls the Next method of the interface until this method returns S_FALSE (1) indicating there are no more items to enumerate.

Pretty simple so far.  So, why can’t you just implement the IEnumVARIANT interface in your collection objects and let the enumerator call the Next method on your class?  Good question!  There are two reasons you can’t do this with VB straight out of the box.

First, the standard definition of the IEnumVARIANT interface uses syntax that VB cannot implement.  This is why you get the dreaded “Not a valid interface for Implements” error if you attempt to implement this interface in a VB class.  To get around this, we need to write a typelib that redefines the syntax of the parameters to the methods in the interface.  This is easily accomplished using Interface Definition Language (IDL) and the MIDL (Make IDL) compiler that ships with Visual Studio.  (See "IDL typelib basics" for more info)

Second, you would need to be able to return a value from the Next method of the IEnumVARIANT interface which is declared as a Sub in VB.  You might be asking, “How do you return a value from a Sub?”  Well, VB’s subs are really functions under the hood.  It’s just that VB handles the return value behind the scenes.  This is because all method calls in VB are implemented via COM and one of the rules of COM is that all methods must return an HRESULT.  VB handles the return for you, returning info about the success or failure of the call and because of this, you can't get access to the return value.  Things are beginning to look a little bit on the impossible side, eh?    Fortunately, functions in VB BAS modules allow you direct access to their return value.  This still leaves the problem of how to get the code that is enumerating the collection to call a function in a BAS module instead of the method on the interface in your collection class.  To do this, we use a routine from Bruce McKinney’s book “Hardcore Visual Basic” that replaces a pointer in a VB class’ Vtable with a pointer to a function in a BAS module.  So, we simply replace the pointer in the Vtable and the Next method is now delegated to a function in a BAS module.

Now we have a collection class that is a bit hacked up in the sense that one of the methods is actually residing in a BAS module.  The main problem with this approach is knowing which instance of the class is associated with the current Next method call from within the Next method since it is now in a totally separate module.  Fortunately, a method call also includes a pointer to the current instance of the class object (in this case an IEnumVARIANT object) which VB normally hides and references internally (see "Under the hood in VB method calls").  Unfortunately, the IEnumVARIANT interface doesn’t have any methods that we can call to return the current item being enumerated.  So how the heck do we get a reference to our enumerator class in which the IEnumVARIANT interface is implemented.  The answer is, we don’t!  Instead, we pull a fast one using some more dirty, underhanded IDL trickery in our typelib.  We simply add a method to the interface that allows us to callback into our class.  A bit of an underhanded hack but  it is what makes this whole scheme work.  Here is the IDL for the interface:

                interface IEnumVARIANTReDef : IUnknown

                {

                      HRESULT Next([in] LONG cElements,

                                            [in, out] VARIANT* aVariants,

                                            [in] LONG lpcElementsFetched);

                      HRESULT Skip([in] LONG cElements);

                      HRESULT Reset();

                      HRESULT Clone([in, out] IEnumVARIANTReDef** lppIEnum);

                      HRESULT GetItems([in] LONG cElements,

                                            [in, out] VARIANT* aVariants,

                                            [in] LONG lpcElementsFetched,

                                            [in, out] LONG* lRetVal);

                };

Notice the GetItems method.  If you look at the standard definition of the IEnumVARIANT interface, you will see that it isn’t there!  When you implement our redefined interface in your class, you get the extra method call.  Now, it’s as simple as calling any VB object to get the data from the correct instance of our enumeration object as evidenced from the call in the IEnumVARIANT_Next function in the BAS module:

                this.GetItems nDummy, vTmp, nDummy, lRet

It doesn’t get any easier than that!  The benefits of implementing your own collection objects are unlimited.  You can even change the way objects are returned in a For Each...Next loop if you have a special need.  Download the fully commented CSuperCollection sample project and kick the VB collection object habit once and for all!  

Download the CSuperCollection sample project  (27KB)