Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / ATL
Article

COM: IEnumXXXX to STL-style iterator wrapper class

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
24 Feb 2000CPOL 58.1K   922   37   1
A simplified method to enumerate a collection of objects.
  • Download source files - 22 Kb
  • Download sample application - 113 Kb
  • The Problem

    Many COM interfaces provide the ability to step through, or enumerate, a collection of some kind. The usual way for a COM interface to expose this kind of functionality is via an interface which conforms to the IEnumXXXX standard.

    IEnum interfaces provide the following methods: Next(), Skip(), Reset() and Clone(). The most interesting is Next() as this allows you to step along the collection that the interface provides access to. Next() is fairly complex though, as it allows you to fetch more than just the next object, you can specify how many objects to fetch and get a whole block in one go. This is to allow for situations where fetching the next object in the enumeration is an expensive operation - such as if the interface referred to a remote object - and you wished to reduce the number of calls to Next().

    Because efficient iteration using an IEnumXXXX interface requires a bit of extra programmer effort to use I decided to write a template to wrap an IEnumXXXX interface in a class which provides an STL style iterator. As an added bonus, you can then write STL style template code which can use the IEnum iterator or any other read only forward iterator.

    The use of this class converts code like this:

    void DoThingWithGUID(GUID &guid)
    {
    }
    void EnumerateGUIDs(IEnumGUID *pIEnum)
    { 
        GUID guids[64];   // Cache up to 64 items each time we call Next()
        ULONG numFetched = 0; 
        while (SUCCEEDED(pIEnum->Next(64, guids, &numFetched))) 
        {
            for (ULONG i = 0; i < numFetched; i++) 
            {
                DoThingWithGuid(guids[i]); 
            } 
        } 
    } 

    into code like this:

    void DoThingWithGUID(GUID &guid)
    {
    }
    void EnumerateGUIDs(IEnumGUID *pIEnum)
    {
        CIEnumGUID it(pIEnum, 64);    // Cache up to 64 items each time we call Next()
        while (it != CIEnumGUID::end())
        {
            DoThingWithGuid(it);
            ++it;
        }
    }


    IEnumIterator<class T, class I, class E>

    IEnumIterator is a template class which wraps an IEnum interface pointer and provides "easier" access to the underlying sequence. The template is designed to be derived from and the derived class passes itself as the first parameter to the template, this is required so that the template can provide correct functionality for its post increment operator (which needs to save a copy of the iterator prior to incrementing it) and for the static end() function which provides access to an iterator that represents the end of any sequence. The second parameter to the template is the IEnum interface that we're providing a wrapper for. The final parameter is the object that the IEnum interface iterates over. By providing this information the IEnumIterator can automatically provide a conversion operator from itself to the underlying object that we're iterating over. This allows you to use the iterator as if it were an instance of the underlying object, thus simplifying code which iterates over the sequence.

    The iterator provides the following functionality:

    • The constructor allows the creator of the iterator to specify the number of items to cache inside the iterator each time the underlying IEnum interface has its Next() member called - this can be changed by the client by a call to setCacheSize().
    • setCacheSize() - allows the client to change the number of items returned each time the underlying IEnum interface has its Next() member called.
    • Pre-increment: ++it - move the iterator to the next item in the sequence.
    • Post-increment: it++ - move the iterator to the next item in the sequence and return a copy of the iterator prior to it being incremented. Note this has a significant overhead when compared to pre-increment. A copy of the current state of the iterator must be made prior to incrementing the iterator. This requires a duplication of the sequence cache and a call to Clone() on the underlying iterator pointer. Post-increment should be avoided unless theses semantics are truly required. To prevent post-increment being used by mistake, the functionality is only included if IENUM_ITERATOR_USE_POST_INC is defined prior to inclusion of the IEnumIterator.hpp file.
    • operator "E"() - cast the iterator to the current item in the sequence. Throws a NullIterator exception if the iterator is no longer valid.
    • Skip() allows you to move the iterator forward by a specified amount.
    • Reset() allows you to position the iterator at the beginning of the sequence again.


    Derived classes

    The IEnumIterator template requires you to derive from it before you can use it. The simplest derived class is shown below:

    class CIterateGUID 
     : public IEnumIterator<CIterateGUID, IEnumGUID, GUID>
    {
    public :
       CIterateGUID(IEnumGUID *pIEnumGUID)
          : IEnumIterator<CIterateGUID, IEnumGUID, GUID>(pIEnumGUID)
          {
          }
    };

    Although often the style of derivation show above is all that you'll need, you can get more adventurous if you like:

    class CIterateCATEGORYINFO 
     : public IEnumIterator<CIterateCATEGORYINFO, IEnumCATEGORYINFO, CATEGORYINFO>
    {
    public :
       CIterateCATEGORYINFO(IEnumCATEGORYINFO *pIEnumCATEGORYINFO)
          : IEnumIterator<CIterateCATEGORYINFO, IEnumCATEGORYINFO, CATEGORYINFO>(pIEnumCATEGORYINFO)
          {
          }
    
       CATID GetCATID() const { return Enumerated().catid; }
       LCID GetLCID() const { return Enumerated().lcid; }
       LPCOLESTR GetDescription() const { return Enumerated().szDescription; }
    };

    The above example provides us with a wrapper to an IEnumCATEGORYINFO interface and allows us to use the iterator to access all of the elements of the underlying CATEGORYINFO object that we're iterating. The call to Enumerated() gives us access to the underlying object and from there we can perform any operations we might like on it. The wrappers shown above aren't strictly necessary, we can always just convert the iterator into an object of the required type and access it directly, but sometimes they might make things neater.


    Ownership of items being iterated over

    My original attempt at this class didn't allow you to use Skip() and Reset(). I decided to add this functionality to make the wrapper more complete. Of course, this added more complications, and it also pointed out some glaring omissions from the first cut of the design... The possibility of skipping items in the sequence means that some items may be fetched from the underlying enumeration interface and cached inside the wrapper but then never accessed. This is fine unless the caller is responsible for managing the lifetime the objects that are returned, as would be the case with an IEnumUknown interface for example. To implement Skip() and Reset() the iterator had to be responsible for the lifetime of the objects it was iterating. This was achieved by having a virtual function in the iterator called to destroy a item each time the iterator was advanced and another virtual function called whenever there was a need to copy an item. The derived class for an CIterateIUnknown iterator would look something like the one shown below:

    class CIterateIUnknown 
     : public IEnumIterator<CIterateIUnknown, IEnumIUnknown, IUnknown *>
    {
    public :
       CIterateIUnknown(IEnumIUnknown *pIEnumIUnknown)
          : IEnumIterator<CIterateIUnknown, IEnumIUnknown, IUnknown *>(pIEnumIUnknown)
          {
          }
    
       virtual void Destroy(IUnknown *pItem) const { pItem->Release(); }
       virtual IUnknown *Copy(IUnknown *pItem) const { pItem->AddRef(); return pItem; }
    };

    This means that the caller is no longer responsible for lifetime of the pointers returned. If the caller wishes to continue to use a pointer after stepping the iterator along, they must call AddRef() on it to take ownership, and then Release() it as normal when they're through.


    Efficiency issues

    In my first attempt design of the iterator, the first call to the underlying interface pointer's Next() member function was made during construction of the IEnumIterator. This meant that if code returned an IEnumIterator to a client who wished to change the number of items cached, the cache has already have been loaded before the client could call setCacheSize(). It also resulted in unnecessary copying of cached items.

    The current version of the code keeps track of whether or not the iterator has been "primed" or not. This makes the code slightly more complex, but only from an implementation point of view. The interface is unchanged for the client. The new code calls Next() for the first time when either the iterator is incremented (if the cache is not primed we need to prime it and then step along one item...) or when the underlying object is accessed (if the cache is not primed we prime it and then return the first item). This means that it's possible for the client to change the cache size before the first call of Next() and that when passing an iterator around by value there's no need to copy a cache of items unless the iterator has been used before it is copied.

    See the article on Len's homepage for the latest updates.

    License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


    Written By
    Software Developer (Senior) JetByte Limited
    United Kingdom United Kingdom
    Len has been programming for over 30 years, having first started with a Sinclair ZX-80. Now he runs his own consulting company, JetByte Limited and has a technical blog here.

    JetByte provides contract programming and consultancy services. We can provide experience in COM, Corba, C++, Windows NT and UNIX. Our speciality is the design and implementation of systems but we are happy to work with you throughout the entire project life-cycle. We are happy to quote for fixed price work, or, if required, can work for an hourly rate.

    We are based in London, England, but, thanks to the Internet, we can work 'virtually' anywhere...

    Please note that many of the articles here may have updated code available on Len's blog and that the IOCP socket server framework is also available in a licensed, much improved and fully supported version, see here for details.

    Comments and Discussions

     
    GeneralCode Format Problem Pin
    Dave Lorde25-Feb-00 11:15
    Dave Lorde25-Feb-00 11:15 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.