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:
Although often the style of derivation show above is all that
class CIterateGUID
: public IEnumIterator<CIterateGUID, IEnumGUID, GUID>
{
public :
CIterateGUID(IEnumGUID *pIEnumGUID)
: IEnumIterator<CIterateGUID, IEnumGUID, GUID>(pIEnumGUID)
{
}
};
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.