ATL Collections and Enumerators
ATL provides the ICollectionOnSTLImpl interface to enable you to quickly implement collection interfaces on your objects. To understand how this class works, you will work through a simple example (below) that uses this class to implement a read-only collection aimed at Visual Basic clients.
To complete this procedure, you will:
First, create a new project and use the ATL COM AppWizard to generate a Simple Object called Words. Make sure that a dual interface called IWords is generated. Objects of the generated class will be used to represent a collection of words (that is, strings).
Now, open the IDL file and add the three properties necessary to turn IWords into a read-only collection interface, as shown below:
[
object,
uuid(0D44F689-B373-11D2-9A7F-50F653C10000),
dual, // (1)
pointer_default(unique),
nonextensible // (2)
]
interface IWords : IDispatch
{
[id(DISPID_NEWENUM), propget] // (3)
HRESULT _NewEnum([out, retval] IUnknown** ppUnk);
[id(DISPID_VALUE), propget] // (4)
HRESULT Item(
[in] long Index,
[out, retval] BSTR* pVal); // (5)
[id(0x00000001), propget] // (6)
HRESULT Count([out, retval] long* pVal);
};
This is the standard form for a read-only collection interface designed with Visual Basic clients in mind. The numbered comments in this interface definition correspond to the comments below:
Once the collection interface is defined, you need to decide how the data will be stored, and how the data will be exposed via the enumerator.
The answers to these questions can be provided in the form of a number of typedefs, which you can add near the top of the header file for your newly created class:
// Store the data in a vector of std::strings
typedef std::vector< std::string > ContainerType;
// The collection interface exposes the data as BSTRs
typedef BSTR CollectionExposedType;
typedef IWords CollectionInterface;
// Use IEnumVARIANT as the enumerator for VB compatibility
typedef VARIANT EnumeratorExposedType;
typedef IEnumVARIANT EnumeratorInterface;
In this case, you will store the data as a std::vector of std::strings. std::vector is an STL container class that behaves like a managed array. std::string is the Standard C++ Library's string class. These classes make it easy to work with a collection of strings.
Since Visual Basic support is vital to the success of this interface, the enumerator returned by the _NewEnum property must support the IEnumVARIANT interface. This is the only enumerator interface understood by Visual Basic.
The typedefs you have created so far provide all the information you need to create further typedefs for the copy classes that will be used by the enumerator and collection:
// Typedef the copy classes using existing typedefs
typedef VCUE::GenericCopy<EnumeratorExposedType, ContainerType::value_type> EnumeratorCopyType;
typedef VCUE::GenericCopy<CollectionExposedType, ContainerType::value_type> CollectionCopyType;
In this example, you can use the custom GenericCopy class defined in VCUE_Copy.h and VCUE_CopyString.h from the ATLCollections project. You can use this class in other code, but you may need to define further specializations of GenericCopy to support data types used in your own collections. For more information, see ATL Copy Policy Classes.
Now all the template parameters necessary to specialize the CComEnumOnSTL and ICollectionOnSTLImpl classes for this situation have been provided in the form of typedefs. To simplify the use of the specializations, create two more typedefs as shown below:
typedef CComEnumOnSTL< EnumeratorInterface, &__uuidof(EnumeratorInterface), EnumeratorExposedType, EnumeratorCopyType, ContainerType > EnumeratorType;
typedef ICollectionOnSTLImpl< CollectionInterface, ContainerType, CollectionExposedType, CollectionCopyType, EnumeratorType > CollectionType;
Now CollectionType is a synonym for a specialization of ICollectionOnSTLImpl that implements the IWords interface defined earlier and provides an enumerator that supports IEnumVARIANT.
Now you must derive CWords from the interface implementation represented by the CollectionType typedef rather than IWords, as shown below:
class ATL_NO_VTABLE CWords :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CWords, &CLSID_Words>,
public IDispatchImpl<CollectionType, &IID_IWords, &LIBID_ATLCOLLECTIONSLib>
{
public:
DECLARE_REGISTRY_RESOURCEID(IDR_STRINGCOLLECTION)
DECLARE_PROTECT_FINAL_CONSTRUCT()
BEGIN_COM_MAP(CWords)
COM_INTERFACE_ENTRY(IWords)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
// IWords
public:
};
The only thing that remains is to populate the vector with data. In this simple example, you can add a few words to the collection in the constructor for the class:
CWords()
{
m_coll.push_back("this");
m_coll.push_back("is");
m_coll.push_back("a");
m_coll.push_back("test");
}
Now, you can test the code with the client of your choice.