COM: IEnumXXXX to STL-style iterator wrapper class

Download Source Code and Example

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.

So that this article and code can be kept up to date more easily I've provided a link to where the article is located on my own web pages. Read the full article and download the source code.



Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds