Enumerator Objects

[Previous] [Next]

In the introduction to this chapter, we described the properties and methods of a collection object. One of those properties is _NewEnum. This property has a COM-defined dispatch ID DISPID_NEWENUM, which is defined as -4. Visual Basic relies on this dispatch ID to retrieve the enumerator object from a collection. The _NewEnum property returns an interface pointer to an enumeration object. The enumeration object must conform to the enumeration contract specified by COM. The enumeration contract is defined in the IEnumxxx interfaces. IEnumxxx isn't an interface itself, but a generic description of the methods required when implementing an enumeration interface. There is no single IEnumxxx interface that can completely describe an enumerator for every type of collection. For collections of COM objects, the enumerator is implemented as an object that supports the IEnumVARIANT interface. COM defines IEnumVARIANT for the common case in which a collection of COM objects that implement IDispatch provides an enumerator. IEnumVARIANT must be used to enable a Visual Basic client to use For/Each syntax. If you don't care about enabling Visual Basic clients to use For/Each, you can implement the enumerator with an IEnumxxx interface for whatever type you like. The contract for an enumerator interface is shown in Table 15-2.

Table 15-2. Enumerator Interface Methods.

IEnumxxx Method Description
Next Retrieves a specified number of items in the enumeration sequence
Skip Skips over a specified number of items in the enumeration sequence
Reset Resets the enumeration sequence to the beginning
Clone Creates another enumerator that contains the same enumeration state as the current one

A client requests an enumerator object pointer by getting the _NewEnum property of the collection. The _NewEnum property returns an IUnknown pointer, which must be queried for the enumeration interface. Visual Basic always requests IEnumVARIANT in a For/Each loop. Once an IEnumVARIANT pointer is found, the Next method is used to request one or more objects in the collection. Currently, Visual Basic only supports retrieving objects one at a time using IEnumVARIANT::Next, but C++ clients can pick an optimum chunk size to retrieve. The enumerator object advances an internal cursor to maintain the current position in the enumeration between calls to Next. IEnumVARIANT::Next requires the number of elements to be returned as a parameter to the caller. The signature for the IEnumVARIANT interface is shown here:

 interface IEnumVARIANT : IUnknown {     virtual HRESULT Next(unsigned long celt,         VARIANT FAR* rgvar,         unsigned long FAR* pceltFetched) = 0;     virtual HRESULT Skip(unsigned long celt) = 0;     virtual HRESULT Reset() = 0;     virtual HRESULT Clone(IEnumVARIANT FAR* FAR* ppenum) = 0; }; 

The methods of an enumeration object are the same, regardless of the type of item being enumerated. However, the Next and Clone methods require parameters that are typed specifically for the individual enumerator. For clarity, these parameters are shown in bold in the preceding code section. Next accepts a pointer to an array (the rgvar parameter) that receives items from the enumerator. The array is typed to match the items contained in the collection. Clone duplicates the enumerator and returns a pointer of the specific enumerator type, shown in the ppenum parameter. Because the implementation of the methods is the same for all IEnumxxx interfaces except the types, a template solution is possible. ATL provides this solution in its two types of enumeration classes. One type is for enumerating collections of items contained in a contiguous memory array (CComEnum classes) and the other type is for enumerating items contained in an STL sequential container (CComEnumOnSTL classes). A collection implemented with ICollectionOnSTLImpl needs the STL version.

CComEnum Enumerator Classes

ATL divides the enumerator declaration and implementation across three classes: CComIEnum, CComEnumImpl, and CComEnum. You typically create an instance of a CComEnum object and return its IUnknown pointer to the client in the _NewEnum property of the collection. The two remaining classes serve as base classes to CComEnum. A summary of each class follows.

CComIEnum

CComIEnum is an abstract base class that derives from IUnknown. CComIEnum declares the Next, Skip, Reset, and Clone methods using a template parameter that defines the type of items being enumerated. For example, an IEnumInteger enumeration interface can be specified in C++ using CComIEnum<int>. You're on your own to supply the matching IDL.

CComEnumImpl

CComEnumImpl derives from an IEnumxxx base class, typically CComIEnum, and supplies the implementations for Next, Skip, Reset, and Clone methods.

CComEnum

CComEnum derives from CComEnumImpl and CComObjectRootEx. CComEnum is an ATL COM object that inherits the enumeration interface implementation from CComEnumImpl. A CComEnum object can be created and returned from _NewEnum as the enumerator object.

Figure 15-2 illustrates the enumerator class hierarchy.

click to view at full size.

Figure 15-2. CComEnum enumerator class hierarchy.

The template specification for CComEnum gives you some control over the hierarchy shown in Figure 15-2. The diagram shows CComIEnum as a base class, but you could instead use IEnumVARIANT or another predefined IEnumxxx interface. CComIEnum is a convenient way to define IEnumxxx interfaces as needed. We'll look at each class in the hierarchy, starting at the top with CComIEnum—which has the fewest template parameters—and working our way down to CComEnum.

CComIEnum<class T>

CComIEnum is templatized on the type of item that is being enumerated. In the following code section, you can see how the Next and Clone methods use the supplied type T:

 template<class T> class ATL_NO_VTABLE CComIEnum : public IUnknown { public:     STDMETHOD(Next)(ULONG celt, T* rgelt,         ULONG* pceltFetched) = 0;     STDMETHOD(Skip)(ULONG celt) = 0;     STDMETHOD(Reset)(void) = 0;     STDMETHOD(Clone)(CComIEnum<T>** ppEnum) = 0; }; 

Déjà vu should be hitting you right about now. This is just a parameterized version of the IEnumVARIANT interface you saw earlier in the chapter. You could say that this is the C++ version of the generic IEnumxxx. CComIEnum<VARIANT> is essentially equivalent to IEnumVARIANT. If you're using an enumerator type not defined by COM, you'll need to create the matching IDL as well. CComIEnum is usually used as the base class for CComEnumImpl.

CComEnumImpl<class Base, const IID* piid, class T, class Copy>

As you would expect from an ATL class ending with Impl, this class implements the interface that it derives from, which is specified in the template parameter Base. The base class must comply with the IEnumxxx contract, as CComIEnum does. The interface ID of the base class is passed in through the piid parameter. The parameter T is the type of object that the interface enumerates over, and the Copy parameter defines the copy policy that will be used to initialize, copy, and destroy items being enumerated. ATL provides copy policy classes for common types through the templatized _Copy class, and you can easily create your own as well. The code for the VARIANT copy policy provided by ATL is shown here:

 template<> class _Copy<VARIANT> { public:     static HRESULT copy(VARIANT* p1, VARIANT* p2)         {return VariantCopy(p1, p2);}     static void init(VARIANT* p) {p->vt = VT_EMPTY;}     static void destroy(VARIANT* p) {VariantClear(p);} }; 

To create a copy policy for a data type not supported by ATL, create a class with all public static members, as in the class above. ATL provides a default copy policy that has no init or destroy code and that performs a shallow copy:

 template <class T> class _Copy { public:     static HRESULT copy(T* p1, T* p2) {memcpy(p1, p2, sizeof(T));         return S_OK;}     static void init(T*) {}     static void destroy(T*) {} }; 

ATL provides specialized versions of the _Copy template class for the following types:

  • VARIANT
  • LPOLESTR
  • OLEVERB
  • CONNECTDATA

For collections of COM interface pointers, ATL has the _CopyInterface class, which calls AddRef on the interface after copying it and releases it in destroy, as shown here:

 template <class T> class _CopyInterface { public:     static HRESULT copy(T** p1, T** p2)     {         *p1 = *p2;         if(*p1)             (*p1)->AddRef();         return S_OK;     }     static void init(T** ) {}     static void destroy(T** p) {if (*p) (*p)->Release();} }; 

CComEnumImpl needs a copy policy to correctly duplicate the enumerated items to your [out] array in the Next method. The policy will also be used if CComEnumImpl is initialized with a snapshot of the collection items, instead of taking ownership of the items.

Ownership is declared in the Init method of CComEnumImpl. Init is called after CComEnumImpl is created to supply the enumerator object with an array of items from the collection. The parameters to Init provide pointers to the beginning and end of a contiguous array of items, along with a flag that describes the ownership of the array. The signature for Init is shown here:

 HRESULT Init(T* begin, T* end, IUnknown* pUnk,     CComEnumFlags flags = AtlFlagNoCopy); 

The flags parameter can be one of three values defined in the CComEnumFlags enumeration type, shown here:

 enum CComEnumFlags {     AtlFlagNoCopy = 0,     AtlFlagTakeOwnership = 2,     AtlFlagCopy = 3 // Copy implies ownership }; 

The default is AtlFlagNoCopy, which allows the enumerator to use the array passed in to Init directly. If the same array is used for enumeration and internal collection state, a client could add or remove items from the array during the lifetime of an enumeration operation—which probably wouldn't be expected by the user of the enumerator. The AtlFlagTakeOwnership flag will also result in direct use of the array; however, the array will also be destroyed when the enumerator object is destroyed. The copy policy is used to destroy each item in the array before deleting the array itself. You might use AtlFlagTakeOwnership if you create an array on the heap in _NewEnum and copy your collection items into it yourself before calling Init. The last flag is AtlFlagCopy, which signals the enumerator to make a copy of the array in the Init method, copying each item to the new array using the copy policy. The copied array is destroyed when the enumerator is destroyed.

CComEnum<class Base, const IID* piid, class T, class Copy, class ThreadModel = CComObjectThreadModel>

CComEnum combines CComEnumImpl with CComObjectRootEx to implement the enumerator object. CComEnum has an interface map with one entry for the base enumeration interface. With the exception of the ThreadModel parameter, the template parameters aren't used directly in CComEnum but are passed on to CComEnumImpl. CComEnum doesn't add any new interesting functionality beyond what it inherits from CComEnumImpl, but this is the class you'll work with to create an enumerator object. To declare an enumerator type that supports IEnumVARIANT, you can use a typedef such as the following.

 typedef CComEnum< CComIEnum<VARIANT>,      &IID_IEnumVARIANT,      VARIANT,      _Copy<VARIANT> > VariantEnumClass; 

You can then use the VariantEnumClass type with CComObject to create an instance of an enumerator object, as shown here:

 VariantEnumClass *pEnum = new CComObject<VariantEnumClass>; 

ATL enumerator objects don't override FinalConstruct, so using the new operator is fine. You can use CreateInstance if you prefer. Once the enumerator object has been created, you need to call Init to set the array of items that will be enumerated. For IEnumVARIANT, you need an array of VARIANTs. The parameters to Init are pointers to the first array item and just beyond the last array item, the IUnknown pointer of the collection object, and the ownership flag. In the code section that follows, the enumerator is initialized from a dynamically allocated array. The enumerator takes ownership of the array, so you don't have to be concerned about destroying it. Also, the enumerator's copy policy will take care of deleting the VARIANTs contained in the array. This example shows the creation of an IEnumVARIANT-compliant enumerator object that enumerates over a collection of objects that implement the ISimpleObject interface.

 typedef CComEnum< CComIEnum<VARIANT>,      &IID_IEnumVARIANT,      VARIANT,      _Copy<VARIANT> > VarEnumClass; CComObject<VarEnumClass> *pEnum = new CComObject<VarEnumClass>; // Allocate and initialize an array of VARIANTs. unsigned int uiSize = sizeof(VARIANT) * m_nCount; VARIANT* pVar = (VARIANT*) new VARIANT[m_nCount]; // Get IDispatch for each collection item and put it in the array. for (int i = 0; i < m_nCount; i++ ) {     VariantInit(&pVar[i]);     ISimpleObject* pIP = GetCollectionItem((long)i+1);     pVar[i].vt = VT_DISPATCH;     if(pIP != NULL)     {         hr = pIP->QueryInterface(IID_IDispatch,             (void**)&pVar[i].pdispVal);         pIP = NULL;     } } // Initialize the enumerator and give it ownership of the array. hr = pEnum->Init(&pVar[0], &pVar[m_nCount],     this, AtlFlagTakeOwnership); 

This code is typical of what you might see in the get_ _NewEnum property of a collection object. Visual Basic calls _NewEnum when it enters a For/Each loop. Visual Basic receives the IUnknown pointer of the enumerator (pEnum) as an [out] parameter from _NewEnum. Visual Basic takes the IUnknown pointer and calls QueryInterface for IEnumVARIANT. With IEnumVARIANT in hand, enumeration commences. Each time through the For/Each loop, Visual Basic calls IEnumVARIANT::Next to retrieve the next object in the collection. As we mentioned earlier in this chapter, there is currently no support for Visual Basic to retrieve more than one item during a call to Next.

CComEnumOnSTL Enumerator Classes

The STL enumerator classes can be used with ICollectionOnSTLImpl to implement the enumerator object for the collection. Similar to the non-STL versions described previously, the ATL classes consist of an enumerator interface definition, an implementation, and an object class that is created and returned to the client in the _NewEnum property of the collection object. ICollectionOnSTLImpl handles all of this. All you need to do is give it the correct type specification for CComEnumOnSTL.

CComEnumOnSTL<class Base, const IID* piid, class T, class Copy, class CollType, class ThreadModel = CComObjectThreadModel>

The template parameters for CComEnumOnSTL are identical to the parameters for CComEnum, with the exception of an additional parameter, CollType, that specifies the kind of STL collection that contains the items. Base defines the IEnumxxx interface the enumerator is implementing, and piid is a pointer to the interface ID for Base. T defines the type of object. The same copy policies described for CComEnumImpl also apply to this Copy template parameter. Part of the class definition for CComEnumOnSTL is shown here:

 template <class Base, const IID* piid, class T, class Copy,     class CollType, class ThreadModel = CComObjectThreadModel> class ATL_NO_VTABLE CComEnumOnSTL :     public IEnumOnSTLImpl<Base, piid, T, Copy, CollType>,     public CComObjectRootEx< ThreadModel > {  } 

As with the non-STL version, most of the enumerator-specific work is done in the IEnumOnSTLImpl class that CComEnumOnSTL derives from. Figure 15-3 shows the inheritance tree for a CComEnumOnSTL enumerator object that uses CComIEnum to define the base interface.

click to view at full size.

Figure 15-3. CComEnumOnSTL enumerator class hierarchy.

The template parameters are forwarded through to IEnumOnSTLImpl, with the exception of the threading model. As you saw earlier in the discussion of ICollectionOnSTLImpl, ICollectionOnSTLImpl::get__NewEnum creates a new CComEnumOnSTL object, initializes it, and returns its IUnknown pointer to the caller. The enumerator object is initialized by calling the Init method, which CComEnumOnSTL inherits from IEnumOnSTLImpl.

IEnumOnSTLImpl <class Base, const IID* piid, class T, class Copy, class CollType>

This class implements the expected Next, Reset, Skip, and Clone methods for the IEnumxxx interface that you specify as the Base template parameter. You can use the same CComIEnum<T> class described in the CComEnum section. For an IEnumVARIANT definition, use CComIEnum<VARIANT> for the Base parameter. The enumerator is initialized after creation by calling the IEnumOnSTLImpl::Init method. Init takes a reference to the STL container as a parameter, as shown here:

 HRESULT Init(IUnknown *pUnkForRelease, CollType& collection) {     m_spUnk = pUnkForRelease;     m_pcollection = &collection;     m_iter = m_pcollection->begin();     return S_OK; } 

Unlike the CComEnumImpl version, there are no options for ownership of the STL container. The enumerator always gets a reference to the caller's STL container in the Init call and therefore never owns the container. The IEnumOnSTLImpl::Clone method follows suit, only giving out the reference to the container, not copying it. IEnumOnSTLImpl caches a pointer to the STL container passed in to Init, along with an iterator for the container.

A nice feature of IEnumOnSTLImpl is that you don't have to allocate an array of VARIANTs to initialize it when creating an IEnumVARIANT implementation. More generally, the IEnumOnSTLImpl::Next method uses the copy policy to get a collection item from the STL container, copy it to the type required by the Next method return value, and return it to the caller. Because IEnumOnSTLImpl has an STL iterator, it can simply dereference the iterator to obtain an item to be copied, regardless of type. In a collection of objects, the STL container holds interface pointers. The copy policy must be prepared to receive the interface pointer for copying and to return a VARIANT VT_DISPATCH. Here's the section of IEnumOnSTLImpl::Next just described:

 T* pelt = rgelt; while (SUCCEEDED(hr) && m_iter != m_pcollection->end()     && nActual < celt) {     hr = Copy::copy(pelt, &*m_iter);  } 

The additional type information passed in for the STL collection in the CollType template parameter makes it possible to have an iterator and a return value with different types. In the previous code section, the return value is of type T, and m_iter is of type CollType::iterator. Only the copy policy needs to know how to copy one to the other.



Inside Atl
Inside ATL (Programming Languages/C)
ISBN: 1572318589
EAN: 2147483647
Year: 1998
Pages: 127

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net