previous page
next page

Standard C++ Collections of ATL Data Types

If you're a fan of the standard C++ library, you might find yourself wanting to keep some of ATL's smart types (such as CComBSTR, CComVariant, CComPtr, and CComQIPtr) in a standard C++ container. Many containers have a requirement concerning the elements they hold that makes this difficult for ATL smart types: operator& must return an address to an instance of the type being held. However, all the smart types except CComVariant overload operator& to return the address of the internal data:

BSTR* CComBSTR:operator&() { return &m_str; }                
T** CComPtr::operator&()   { ATLASSERT(p==NULL); return &p; }
T** CComQIPtr::operator&() { ATLASSERT(p==NULL); return &p; }

These overloads mean that CComBSTR, CComPtr, and CComQIPtr cannot be used in many C++ containers or with standard C++ algorithms with the same requirement. The classic workaround for this problem is to maintain a container of a type that holds the ATL smart type but that doesn't overload operator&. ATL provides the CAdapt class for this purpose.

ATL Smart Type Adapter

The CAdapt class is provided for the sole purpose of wrapping ATL smart types for use in C++ containers. It's parameterized to accept any of the current or future such types:

template <class T> class CAdapt {            
public:                                      
    CAdapt() { }                             
                                             
    CAdapt(__in const T& rSrc) :             
        m_T( rSrc )                          
    { }                                      
                                             
    CAdapt(__in const CAdapt& rSrCA) :       
        m_T( rSrCA.m_T )                     
    { }                                      
                                             
    CAdapt& operator=(__in const T& rSrc)    
    { m_T = rSrc; return *this; }            
                                             
    bool operator<(__in const T& rSrc) const 
    { return m_T < rSrc; }                   
                                                 
    bool operator==(__in const T& rSrc) const
    { return m_T == rSrc; }                  
                                              
    operator T&()                            
    { return m_T; }                          
                                               
    operator const T&() const                
    { return m_T; }                          
                                             
T m_T;                                       
};                                           

Notice that CAdapt does not have an operator&, so it works just fine for C++ containers and collections. Also notice that the real data is held in a public member variable called m_T. Typical usage requires using either this data member or a static_cast to obtain the underlying data.

CAdapt Usage

For example, imagine that you want to expose prime numbers as words instead of digits. Of course, you'd like the collection to support multiple languages, so you want to expose the strings in Unicode. Also, you'd like to support type-challenged COM mappings, so the strings have to be BSTRs. These requirements suggest the following interface:

[ object, dual]
interface IPrimeNumberWords : IDispatch {
  HRESULT CalcPrimes([in] long min, [in] long max);

  [propget]
  HRESULT Count([out, retval] long* pnCount);

  [propget, id(DISPID_VALUE)]
  HRESULT Item([in] long n,
    [out, retval] BSTR* pbstrPrimeWord);

  [propget, id(DISPID_NEWENUM)]
  HRESULT _NewEnum([out, retval] IUnknown** ppunkEnum);
};

Notice that the Item property exposes the prime number as a string, not a number. Also keep in mind that although the signature of _NewEnum is unchanged, we will be returning VARIANTs to the client that contain BSTRs, not long numbers.

Because we're dealing with one of the COM data types that's inconvenient for C++ programmers, BSTRs, we'd like to use the CComBSTR smart data type described in Chapter 3, "ATL Smart Types." The compiler doesn't complain if we use a data member like this to maintain the data:

vector<CComBSTR> m_rgPrimes;

Unfortunately, depending on what we do with the vector, some obscure runtime errors can result because of CComBSTR's overloaded operator&. Instead, we use CAdapt to hold the data:

vector< CAdapt<CComBSTR> > m_rgPrimes;

Of course, because we're using strings, our method implementations change. To calculate the data, we change the prime numbers to strings:

STDMETHODIMP CPrimeNumberWords::CalcPrimes(long min, long max) {
  while (min <= max) {
    if (IsPrime(min)) {
      char sz[64];
      CComBSTR bstr = NumWord(min, sz);
      m_rgPrimes.push_back(bstr);
    }
    ++min;
  }

  return S_OK;
}

Notice how we can simply push a CComBSTR onto the vector. The compiler uses the CAdapt<CComBSTR> constructor that takes a const CComBSTR& to construct the appropriate object for the vector to manage. The get_Count method doesn't change, but the get_Item method does:

STDMETHODIMP CPrimeNumberWords::get_Item(long n,
  BSTR* pbstrPrimeWord) {
  if (n < 1 || n > m_rgPrimes.size()) return E_INVALIDARG;

  CComBSTR& bstr = m_rgPrimes[n-1].m_T;
  return bstr.CopyTo(pbstrPrimeWord);
}

Notice that we're reaching into the vector and pulling out the appropriate element. Again, remember that the type of element we're holding is CAdapt<CComBSTR>, so I've used the m_T element to access the CComBSTR data inside. However, because the CAdapt<CComBSTR> class has an implicit cast operator to CComBSTR&, using the m_T member explicitly is not necessary.

Finally, the get__NewEnum method must also change. Remember that we're implementing IEnumVARIANT, but instead of holding long numbers, we're holding BSTRs. Therefore, the on-demand data conversion must convert between a CAdapt<CComBSTR> (the data type held in the container) to a VARIANT holding a BSTR. This can be accomplished with another custom copy policy class:

struct _CopyVariantFromAdaptBstr {
    static HRESULT copy(VARIANT* p1, CAdapt<CComBSTR>* p2) {
    p1->vt = VT_BSTR;
    p1->bstrVal = p2->m_T.Copy();
    return (p1->bstrVal ? S_OK : E_OUTOFMEMORY);
  }

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

The corresponding enumeration type definition looks like this:

typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT,
                        _CopyVariantFromAdaptBstr,
                        vector< CAdapt<CComBSTR> > >
        CComEnumVariantOnVectorOfAdaptBstr;

Using these two type definitions, implementing get__NewEnum looks much like it always does:

STDMETHODIMP CPrimeNumberWords::get__NewEnum(
  IUnknown** ppunkEnum) {
  *ppunkEnum = 0;

  CComObject<CComEnumVariantOnVectorOfAdaptBstr>* pe = 0;
  HRESULT hr = pe->CreateInstance(&pe);
  if( SUCCEEDED(hr) ) {
    pe->AddRef();

    hr = pe->Init(this->GetUnknown(), m_rgPrimes);
    if (SUCCEEDED(hr)) {
      hr = pe->QueryInterface(ppunkEnum);
    }

    pe->Release();
  }

  return hr;
}

Using ICollectionOnSTLImpl with CAdapt

If you want to combine the use of ICollectionOnSTLImpl with CAdapt, you already have half the tools: the custom copy policy and the enumeration type definition. You still need another custom copy policy that copies from the vector of CAdapt<CComBSTR> to the BSTR* that the client provides to implement get_Item. This copy policy can be implemented like this:

struct _CopyBstrFromAdaptBstr {
    static HRESULT copy(BSTR* p1, CAdapt<CComBSTR>* p2) {
    *p1 = SysAllocString(p2->m_T);
    return (p1 ? S_OK : E_OUTOFMEMORY);
  }

  static void init(BSTR* p) { }
  static void destroy(BSTR* p) { SysFreeString(*p); }
};

Finally, we can use CAdapt with ICollectionOnSTLImpl like this:

typedef IDispatchImpl<IPrimeNumberWords, &IID_IPrimeNumberWords>
        IPrimeNumberWordsDualImpl;

typedef ICollectionOnSTLImpl<IPrimeNumberWordsDualImpl,
                                vector< CAdapt<CComBSTR> >,
                                BSTR,
                                _CopyBstrFromAdaptBstr,
                                CComEnumVariantOnVectorOfAdaptBstr>
        IPrimeNumberWordsCollImpl;

class ATL_NO_VTABLE CPrimeNumberWords :
  public CComObjectRootEx<CComSingleThreadModel>,
  public CComCoClass<CPrimeNumberWords,
    &CLSID_PrimeNumberWords>,
  public IPrimeNumberWordsCollImpl {
public:
...
// IPrimeNumberWords
public:
  STDMETHODIMP CalcPrimes(long min, long max) {
    while (min <= max) {
      if (IsPrime(min)) {
      char        sz[64];
      CComBSTR bstr = NumWord(min, sz);
      m_coll.push_back(bstr);
      }
      ++min;
    }

    return S_OK;
  }
};


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)