Edit

Share via


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:

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:

  1. Collection interfaces are usually dual because Automation clients accesses the _NewEnum property via IDispatch::Invoke. However, Automation clients can access the remaining methods via the vtable, so dual interfaces are preferable to dispinterfaces.

  2. 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.

  3. 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.)

  4. 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.

  5. 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.

  6. 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