ICollectionOnSTLImplIn addition to parameterized implementations of enumeration interfaces, ATL provides parameterized implementations of collection interfaces, assuming that you're willing to keep your data in a standard C++-like container. The implementation is provided by the ICollectionOnSTLImpl class: template <class T, class CollType, class ItemType, class CopyItem, class EnumType> class ICollectionOnSTLImpl : public T { public: STDMETHOD(get_Count)(long* pcount); STDMETHOD(get_Item)(long Index, ItemType* pvar); STDMETHOD(get__NewEnum)(IUnknown** ppUnk); CollType m_coll; }; The ICollectionOnSTLImpl class provides an implementation of the three standard collection properties much like what I showed you earlier. The chief difference is that the container is managed for you in the m_coll member data of the ICollectionOnSTLImpl class. That means that you can't provide a copy of the data to the enumerators, but you can still use a collection that calculates on demand and you can still convert from a convenient type to the type required by the enumerator exposed from get__NewEnum. This is because, although you get to decide the type of the container in a template parameter, you're no longer implementing get__NewEnum. The template parameters of ICollectionOnSTLImpl are as follows:
ICollectionOnSTLImpl UsageThe best way to understand the ICollectionOnSTLImpl class is to see it in action. The first C++based implementation of the IPrimesCollection standard collection interface assumed that we wanted to manage a precalculated container of VARIANTs. This can be done using ICollectionOnSTLImpl: // Needed for implementation of get_Item. // Converts the storage type (VARIANT) to the item type (long). struct _CopyLongFromVariant { static HRESULT copy(long* p1, VARIANT* p2) { if (p2->vt == VT_I4) { *p1 = p2->lVal; return S_OK; } else { VARIANT var; HRESULT hr = VariantChangeType(&var, p2, 0, VT_I4); if (SUCCEEDED(hr)) *p1 = var.lVal; return hr; } } static void init(long* p) { } static void destroy(long* p) { } }; // Needed for implementation of IDispatch methods typedef IDispatchImpl<IPrimeNumbers, &IID_IPrimeNumbers> IPrimeNumbersDualImpl; // Needed for implementation of get__NewEnum method typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT, _Copy<VARIANT>, vector<VARIANT> > ComEnumVariantOnVector; // Needed for implementation of standard collection methods typedef ICollectionOnSTLImpl<IPrimeNumbersDualImpl, vector<VARIANT>, long, _CopyLongFromVariant, CComEnumVariantOnVector> IPrimeNumbersCollImpl; class ATL_NO_VTABLE CPrimeNumbers : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CPrimeNumbers, &CLSID_PrimeNumbers>, public IPrimeNumbersCollImpl { public: ... // IPrimeNumbers public: STDMETHODIMP CalcPrimes(long min, long max) { m_coll.clear(); for (long n = min; n <= max; ++n) { if (IsPrime(n)) { VARIANT var = {VT_I4}; var.lVal = n; m_coll.push_back(var); } } return S_OK; } }; If we wanted to precalculate the prime numbers but keep them as a vector of long numbers, this is how we'd use ICollectionOnSTLImpl: // Needed for implementation of get__NewEnum. // Converts the storage type (long) to the // enumeration type (VARIANT). struct _CopyVariantFromLong { static HRESULT copy(VARIANT* p1, long* p2) { if (p1->vt == VT_I4) { *p2 = p1->lVal; return S_OK; } else { VARIANT var; HRESULT hr = VariantChangeType(&var, p1, 0, VT_I4); if( SUCCEEDED(hr) ) *p2 = var.lVal; return hr; } } static void init(VARAINT* p) { ::VariantInit(p); } static void destroy(VARIANT* p) { ::VariantClear(p); } }; // Needed for implementation of IDispatch methods typedef IDispatchImpl<IPrimeNumbers, &IID_IPrimeNumbers> IPrimeNumbersDualImpl; // Needed for implementation of get__NewEnum method typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT, _CopyLongFromVariant, vector<VARIANT> > CComEnumVariantOnVectorOfLongs; // Needed for implementation of standard collection methods typedef ICollectionOnSTLImpl<IPrimeNumbersDualImpl, vector<long>, long, _Copy<long>, CComEnumVariantOnVectorOfLongs> IPrimeNumbersCollImpl; class ATL_NO_VTABLE CPrimeNumbers : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CPrimeNumbers, &CLSID_PrimeNumbers>, public IPrimeNumbersCollImpl { public: ... // IPrimeNumbers public: STDMETHODIMP CalcPrimes(long min, long max) { m_coll.clear(); for (long n = min; n <= max; ++n) { if (IsPrime(n)) { m_coll.push_back(n); } } return S_OK; } }; Finally, if we wanted to have the prime numbers calculated on demand and exposed as long numbers, we'd use ICollectionOnSTLImpl: // Calculates prime numbers on demand class PrimesContainer; // Needed for implementation of get_Item. // Converts the storage type (VARIANT) to the item type (long). struct _CopyVariantFromLong; // Needed for implementation of IDispatch methods typedef IDispatchImpl<IPrimeNumbers, &IID_IPrimeNumbers> IPrimeNumbersDualImpl; // Needed for implementation of get__NewEnum method typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT, _CopyVariantFromLong, PrimesContainer > CComEnumVariantOnPrimesContainer; // Needed for implementation of standard collection methods typedef ICollectionOnSTLImpl<IPrimeNumbersDualImpl, PrimesContainer, long, _Copy<long>, CComEnumVariantOnPrimesContainer> IPrimeNumbersCollImpl; class ATL_NO_VTABLE CPrimeNumbers : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CPrimeNumbers, &CLSID_PrimeNumbers>, public IPrimeNumbersCollImpl { public: ... // IPrimeNumbers public: STDMETHODIMP CalcPrimes(long min, long max) { m_coll.SetRange(min, max); } }; Jim Springfield, the father of ATL, says "ICollectionOnSTLImpl is not for the faint of heart." He's absolutely right. It provides a lot of flexibility, but at the expense of complexity. Still, when you've mastered the complexity, as with any good class library, you can get a lot done with very little code. |