none
How do I create an ATL collection of ATL objects?

    Question

  • Hi.

    I'm trying to create an ATL collection object that contains ATL simple objects.

    I am following the information here: http://msdn.microsoft.com/en-us/library/3stwxh95(VS.80).aspx (Implementing an STL-Based Collection) as well as referring to the ATLCollections sample code.

    All I want to do is have a collection of IMyObject objects, which are ATL simple objects (which work fine on their own), that I can expose to clients.

    I have created the collection object, called MyObjects. In MyObjects.h I have tried to define the MyObjectsColl typedefs as follows:

    namespace MyObjectsColl
    {
      typedef IMyObject OriginalType;
      typedef std::vector< OriginalType >  ContainerType;
      typedef OriginalType                        CollectionExposedType;
      typedef IMyObjects                              CollectionInterface;

      // Use IEnumVARIANT as the enumerator for VB compatibility
      typedef VARIANT                            EnumeratorExposedType;
      typedef IEnumVARIANT                       EnumeratorInterface;

      // Typedef the copy classes using existing typedefs
      typedef VCUE::GenericCopy<EnumeratorExposedType, ContainerType::value_type>    EnumeratorCopyType;
      typedef VCUE::GenericCopy<CollectionExposedType, ContainerType::value_type>    CollectionCopyType;

      typedef CComEnumOnSTL< EnumeratorInterface, &__uuidof(EnumeratorInterface), EnumeratorExposedType, EnumeratorCopyType, ContainerType >    EnumeratorType;   
      typedef ICollectionOnSTLImpl< CollectionInterface, ContainerType, CollectionExposedType, CollectionCopyType, EnumeratorType >    CollectionType;
    };

    I have included the files VCUE_Collection.h and VCUE_Copy.h from the documentation in my project.

    When I compile this I get the error "IMyObject cannot instantiate abstract class". I assume this is because the vector contains actual objects instead of pointers and the IMyObject interface cannot be instantiated in this way.

    So if I change OriginalType to be IMyObject* and compile it I get the error "error C2664: 'ATL::_Copy<VARIANT>::copy' : cannot convert parameter 2 from 'IMyObject **' to 'const VARIANT *' c:\projects\atltest\reuse\vcue_copy.h 45".

    This has something to do with the copy method in the GenericCopy class of the VCUE code that I included from the documentation. I think either I have done something wrong in my typedefs or I need to provide a different copy policy class (http://msdn.microsoft.com/en-us/library/td6kz9x0(VS.80).aspx). However, I have no idea what the code is doing here or what it is expecting me to provide. I have read the copy policy documentation and dont understand it.

    Surely there must be a simple way of achieving this (an ATL collection of ATL objects). Can anyone help with what that approach might be?

    Friday, July 31, 2009 4:33 PM

Answers

  • I have managed to get this working now.

    The method signatures for the _CopyVariantFromAdaptItf::copy and _CopyItfFromAdaptItf::copy methods needed to both be changed to use the const keyword:

    _CopyVariantFromAdaptItf::copy:
    static HRESULT copy(VARIANT* p1, const CAdapt< CComPtr<T> >* p2)

    _CopyItfFromAdaptItf::copy
    static HRESULT copy(T** p1, const CAdapt< CComPtr<T> >* p2)

    To make things easier when creating new collections of COM objects i've created a couple of new template definitions that override CComEnumOnSTL and ICollectionOnSTLImpl so that all I need to do is specify the COM interface type to be held in the collection as well as the name of the collection's interface. So the full set of template definitions that I now have in a file called ComCollection.h are:

    --------------

    #pragma once

    #include <vector>
    using namespace std;

    template <typename T>
    struct _CopyVariantFromAdaptItf
    {
      static HRESULT copy(VARIANT* p1, const CAdapt< CComPtr<T> >* p2)
      {
        HRESULT hr = p2->m_T->QueryInterface(
          IID_IDispatch,
          (void**)&p1->pdispVal
          );
        if (SUCCEEDED(hr))
        {
          p1->vt = VT_DISPATCH;
        }
        else
        {
          hr = p2->m_T->QueryInterface(
            IID_IUnknown,
            (void**)&p1->punkVal
            );
          if( SUCCEEDED(hr) )
          {
            p1->vt = VT_UNKNOWN;
          }
        }

        return hr;
      }

      static void init(VARIANT* p) { VariantInit(p); }
      static void destroy(VARIANT* p) { VariantClear(p); }
    };

    template <typename T>
    struct _CopyItfFromAdaptItf
    {
      static HRESULT copy(T** p1, const CAdapt< CComPtr<T> >* p2)
      {
        if( *p1 = p2->m_T )
        {
          return (*p1)->AddRef(), S_OK;
        }
        return E_POINTER;
      }

      static void init(T** p) {}
      static void destroy(T** p) { if( *p ) (*p)->Release(); }
    };

    template< typename T >
    struct ComCollEnum : public CComEnumOnSTL<
                                    IEnumVARIANT,
                                    &IID_IEnumVARIANT,
                                    VARIANT,
                                    _CopyVariantFromAdaptItf<T>,
                                    vector< CAdapt< CComPtr<T> > >
                                    >
    {
    };

    template < typename ItemTypeT, typename CollectionTypeT >
    struct ComCollImpl : public ICollectionOnSTLImpl<
                                  IDispatchImpl<CollectionTypeT, &__uuidof(CollectionTypeT)>,
                                  vector< CAdapt< CComPtr<ItemTypeT> > >,
                                  ItemTypeT*,
                                  _CopyItfFromAdaptItf< ItemTypeT >,
                                  ComCollEnum< ItemTypeT >
                                  >
    {
    };

    -----------------

    And to create a new collection class the class is defined as follows:

    #include "ComCollection.h"

    class ATL_NO_VTABLE CMyObjects :
     public CComObjectRootEx<CComSingleThreadModel>,
     public CComCoClass<CMyObjects, &CLSID_MyObjects>,
     public IDispatchImpl<ComCollImpl<IMyObject, IMyObjects>, &IID_IMyObjects, &LIBID_MYTESTLib, /*wMajor =*/ 1, /*wMinor =*/ 0>

    The key part is the ComCollImpl<IMyObject, IMyObjects>, which allows me to specify the collection type info. The class doesnt need to contain any methods since they are implemented automatically by ICollectionOnSTLImpl.

    • Marked as answer by Wesley Yao Thursday, August 06, 2009 5:28 AM
    Tuesday, August 04, 2009 10:00 AM

All replies

  • Chapter 7 of ATL Internals by Rector and Sells covers collections and enumerators in ATL. It's over 50 pages of dense material so it's pretty hard to summarize here. ICollectionOnSTLImpl sounds like the right class for you.
    Friday, July 31, 2009 4:45 PM
  • Thank you for the reply.

    Firstly, I do not have that book. Secondly, I am currently using ICollectionOnSTLImpl in the approach I mention above (it is the CollectionType enum).

    Surely someone can provide a sample of what i'm trying to achieve. I would have thought it to be a failrly common thing to create an ATL collection object containing ATL objects. The documentation and sample code on MSDN is too simplistic and just uses strings or numbers.
    Friday, July 31, 2009 5:05 PM
  • Ok, I have managed to get a copy of the book now.

    It seems what I need to do is described in the Object Models section in Chapter 8. So I created a new ATL project to try out the code in the book.

    Code compiles fine until the header file for the ATL collection object (MyDocuments.h) is included in another class (the one that will provide access to the collection object). When its included I then get the following error:

    Error 1 error C2664: '_CopyItfFromAdaptItf<T>::copy' : cannot convert parameter 2 from 'const ATL::CAdapt<T> *__w64 ' to 'ATL::CAdapt<T> *' c:\program files\microsoft visual studio 8\vc\atlmfc\include\atlcom.h 5126

    All of the code for the ATL collection object is pretty much straight out of the book. Here's the code:

    // MyDocuments.h : Declaration of the CMyDocuments

    #pragma once
    #include "resource.h"       // main symbols

    #include "Document.h"
    #include "MyDocument.h"

    #include <list>

    using namespace std;


    #if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA)
    #error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms."
    #endif

    template <typename T>
    struct _CopyVariantFromAdaptItf
    {
      static HRESULT copy(VARIANT* p1, CAdapt< CComPtr<T> >* p2)
      {
        HRESULT hr = p2->m_T->QueryInterface(
                                      IID_IDispatch,
                                      (void**)&p1->pdispVal
                                      );
        if (SUCCEEDED(hr))
        {
          p1->vt = VT_DISPATCH;
        }
        else
        {
          hr = p2->m_T->QueryInterface(
                                  IID_IUnknown,
                                  (void**)&p1->punkVal
                                  );
          if( SUCCEEDED(hr) )
          {
            p1->vt = VT_UNKNOWN;
          }
        }

        return hr;
      }

      static void init(VARIANT* p) { VariantInit(p); }
      static void destroy(VARIANT* p) { VariantClear(p); }
    };

    typedef CComEnumOnSTL<
                  IEnumVARIANT,
                  &IID_IEnumVARIANT,
                  VARIANT,
                  _CopyVariantFromAdaptItf<IMyDocument>,
                  list< CAdapt< CComPtr<IMyDocument> > >
                  > CComEnumVariantOnListOfDocuments;

    template <typename T>
    struct _CopyItfFromAdaptItf
    {
      static HRESULT copy(T** p1, CAdapt< CComPtr<T> >* p2)
      {
        if( *p1 = p2->m_T )
        {
          return (*p1)->AddRef(), S_OK;
        }
        return E_POINTER;
      }

      static void init(T** p) {}
      static void destroy(T** p) { if( *p ) (*p)->Release(); }
    };

    typedef ICollectionOnSTLImpl<
                          IDispatchImpl<IMyDocuments, &IID_IMyDocuments>,
                          list< CAdapt< CComPtr<IMyDocument> > >,
                          IMyDocument*,
                          _CopyItfFromAdaptItf<IMyDocument>,
                          CComEnumVariantOnListOfDocuments
                          > IDocumentsCollImpl;

    class ATL_NO_VTABLE CMyDocuments :
      public CComObjectRootEx<CComSingleThreadModel>,
      public CComCoClass<CMyDocuments>, // noncreateable
      public IDocumentsCollImpl
    {
    public:
      DECLARE_NO_REGISTRY()
      DECLARE_NOT_AGGREGATABLE(CMyDocuments)
      DECLARE_PROTECT_FINAL_CONSTRUCT()

      BEGIN_COM_MAP(CMyDocuments)
        COM_INTERFACE_ENTRY(IMyDocuments)
        COM_INTERFACE_ENTRY(IDispatch)
      END_COM_MAP()

      // IDocuments
    public:
      STDMETHODIMP AddDocument(IMyDocument** ppDocument)
      {
        // Create a document to hand back to the client
        HRESULT hr = CMyDocument::CreateInstance(ppDocument);
        if( SUCCEEDED(hr) ) {
          // Put the document on the list
          CComPtr<IMyDocument> spDoc = *ppDocument;
          m_coll.push_back(spDoc);
        }

        return hr;
      }
    };


    If this header file is included in another class then I get the above compilation error. What am I doing wrong now!!!?

    Monday, August 03, 2009 2:32 PM
  • Quick questions: Are you compiling under x64? Are all modules using the same x64 or x86? Which version of Visual Studio?
    Monday, August 03, 2009 5:57 PM
  • I have managed to get this working now.

    The method signatures for the _CopyVariantFromAdaptItf::copy and _CopyItfFromAdaptItf::copy methods needed to both be changed to use the const keyword:

    _CopyVariantFromAdaptItf::copy:
    static HRESULT copy(VARIANT* p1, const CAdapt< CComPtr<T> >* p2)

    _CopyItfFromAdaptItf::copy
    static HRESULT copy(T** p1, const CAdapt< CComPtr<T> >* p2)

    To make things easier when creating new collections of COM objects i've created a couple of new template definitions that override CComEnumOnSTL and ICollectionOnSTLImpl so that all I need to do is specify the COM interface type to be held in the collection as well as the name of the collection's interface. So the full set of template definitions that I now have in a file called ComCollection.h are:

    --------------

    #pragma once

    #include <vector>
    using namespace std;

    template <typename T>
    struct _CopyVariantFromAdaptItf
    {
      static HRESULT copy(VARIANT* p1, const CAdapt< CComPtr<T> >* p2)
      {
        HRESULT hr = p2->m_T->QueryInterface(
          IID_IDispatch,
          (void**)&p1->pdispVal
          );
        if (SUCCEEDED(hr))
        {
          p1->vt = VT_DISPATCH;
        }
        else
        {
          hr = p2->m_T->QueryInterface(
            IID_IUnknown,
            (void**)&p1->punkVal
            );
          if( SUCCEEDED(hr) )
          {
            p1->vt = VT_UNKNOWN;
          }
        }

        return hr;
      }

      static void init(VARIANT* p) { VariantInit(p); }
      static void destroy(VARIANT* p) { VariantClear(p); }
    };

    template <typename T>
    struct _CopyItfFromAdaptItf
    {
      static HRESULT copy(T** p1, const CAdapt< CComPtr<T> >* p2)
      {
        if( *p1 = p2->m_T )
        {
          return (*p1)->AddRef(), S_OK;
        }
        return E_POINTER;
      }

      static void init(T** p) {}
      static void destroy(T** p) { if( *p ) (*p)->Release(); }
    };

    template< typename T >
    struct ComCollEnum : public CComEnumOnSTL<
                                    IEnumVARIANT,
                                    &IID_IEnumVARIANT,
                                    VARIANT,
                                    _CopyVariantFromAdaptItf<T>,
                                    vector< CAdapt< CComPtr<T> > >
                                    >
    {
    };

    template < typename ItemTypeT, typename CollectionTypeT >
    struct ComCollImpl : public ICollectionOnSTLImpl<
                                  IDispatchImpl<CollectionTypeT, &__uuidof(CollectionTypeT)>,
                                  vector< CAdapt< CComPtr<ItemTypeT> > >,
                                  ItemTypeT*,
                                  _CopyItfFromAdaptItf< ItemTypeT >,
                                  ComCollEnum< ItemTypeT >
                                  >
    {
    };

    -----------------

    And to create a new collection class the class is defined as follows:

    #include "ComCollection.h"

    class ATL_NO_VTABLE CMyObjects :
     public CComObjectRootEx<CComSingleThreadModel>,
     public CComCoClass<CMyObjects, &CLSID_MyObjects>,
     public IDispatchImpl<ComCollImpl<IMyObject, IMyObjects>, &IID_IMyObjects, &LIBID_MYTESTLib, /*wMajor =*/ 1, /*wMinor =*/ 0>

    The key part is the ComCollImpl<IMyObject, IMyObjects>, which allows me to specify the collection type info. The class doesnt need to contain any methods since they are implemented automatically by ICollectionOnSTLImpl.

    • Marked as answer by Wesley Yao Thursday, August 06, 2009 5:28 AM
    Tuesday, August 04, 2009 10:00 AM
  • Congratulations!
    Tuesday, August 04, 2009 4:22 PM
  • For a comprehensive example of how to properly declare, extend, implement and manipulate collections of COM objects via ATL have a look at http://www.prosyslib.org

     

    Monday, May 03, 2010 4:35 PM