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;
}
};
|