Implementing a C++ Standard Library-Based Collection
ATL provides the ICollectionOnSTLImpl
interface to enable you to quickly implement C++ Standard Library-based 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 Automation clients.
The sample code is from the ATLCollections sample.
To complete this procedure, you will:
Edit the IDL file for the generated interface.
Create five typedefs describing how the collection items are stored and how they will be exposed to clients via COM interfaces.
Create typedefs for the enumerator and collection implementations.
Edit the wizard-generated C++ code to use the collection typedef.
Generating a New Simple Object
Create a new project, ensuring that the Attributes box under Application Settings is cleared. Use the ATL Add Class dialog box and Add Simple Object Wizard 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).
Editing the IDL File
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(7B3AC376-509F-4068-87BA-03B73ADC359B),
dual, // (1)
nonextensible, // (2)
pointer_default(unique)
]
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 Automation clients in mind. The numbered comments in this interface definition correspond to the comments below:
Collection interfaces are usually dual because Automation clients accesses the
_NewEnum
property viaIDispatch::Invoke
. However, Automation clients can access the remaining methods via the vtable, so dual interfaces are preferable to dispinterfaces.If a dual interface or dispinterface will not be extended at run time (that is, you won't provide extra methods or properties via
IDispatch::Invoke
), you should apply the nonextensible attribute to your definition. This attribute enables Automation clients to perform full code verification at compile time. In this case, the interface should not be extended.The correct DISPID is important if you want Automation clients to be able to use this property. (Note that there is only one underscore in DISPID_NEWENUM.)
You can supply any value as the DISPID of the
Item
property. However,Item
typically uses DISPID_VALUE to make it the default property of the collection. This allows Automation clients to refer to the property without naming it explicitly.The data type used for the return value of the
Item
property is the type of the item stored in the collection as far as COM clients are concerned. The interface returns strings, so you should use the standard COM string type, BSTR. You can store the data in a different format internally as you'll see shortly.The value used for the DISPID of the
Count
property is completely arbitrary. There's no standard DISPID for this property.
Creating Typedefs for Storage and Exposure
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 a C++ Standard Library container class that behaves like a managed array. std::string is the C++ Standard 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.
Creating Typedefs for Copy Policy Classes
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 sample. 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.
Creating Typedefs for Enumeration and Collection
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
.
Editing the Wizard-Generated Code
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>,
// 'CollectionType' replaces 'IWords' in next line
public IDispatchImpl<CollectionType, &IID_IWords, &LIBID_NVC_ATL_COMLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
{
public:
DECLARE_REGISTRY_RESOURCEID(IDR_WORDS)
BEGIN_COM_MAP(CWords)
COM_INTERFACE_ENTRY(IWords)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
// Remainder of class declaration omitted.
Adding Code to Populate the Collection
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.
See also
Collections and Enumerators
ATLCollections Sample
ATL Copy Policy Classes