Chapter 3. ATL Smart Types

VARIANTs, SAFEARRAYs, and Interface Pointers

COM has a number of data types beyond the numeric types available in the C and C++ languages. Three such data types are the VARIANT data type, interface pointers, and the SAFEARRAY data type. ATL provides useful classes that encapsulate each of these data types and their special idiosyncrasies.

The CComVariant class is a smart VARIANT class. The class implements the special initialization, copy, and destruction semantics of the COM VARIANT data type. CComVariant instances can be used in most, but not all, of the places you would use a VARIANT.

The CComPtr, CComQIPtr, and CComGITPtr classes are smart pointer classes. Smart pointer classes are definitions of objects that “act” like a pointer; specifically, a pointer with extra semantics. The major additional semantic for the ATL smart pointer classes is automatic reference counting, which eliminates entire classes of errors.

The CComSafeArray class manages the Automation array type SAFEARRAY. These arrays require special handling with dedicated API functions for allocating, deallocating, and interrogating members of the array.

The CComVariant Smart VARIANT Class

A Review of the COM VARIANT Data Type

Occasionally while using COM, you’ll want to pass parameters to a method without any knowledge of the data types the method requires. For the method to be capable of interpreting its received parameters, the caller must specify the format of the data as well as its value.

Alternatively, you can call a method that returns a result that consists of varying data types, depending on context. Sometimes it returns a string, sometimes a long, and sometimes even an interface pointer. This requires the method to return data in a self-describing data format. For each value transmitted, you send two fields: a code specifying a data type and a value represented in the specified data type. Clearly, for this to work, the sender and receiver must agree on the set of possible formats.

COM specifies one such set of possible formats (the VARTYPE enumeration) and specifies the structure that contains both the format and an associated value (the VARIANT structure). The VARIANT structure looks like this:

 1typedef struct FARSTRUCT tagVARIANT VARIANT;
 2typedef struct FARSTRUCT tagVARIANT VARIANTARG;
 3
 4typedef struct tagVARIANT {
 5    VARTYPE vt;
 6    unsigned short wReserved1;
 7    unsigned short wReserved2;
 8    unsigned short wReserved3;
 9    union {
10        unsigned char         bVal;           // VT_UI1
11        short                 iVal;           // VT_I2
12        long                  lVal;           // VT_I4
13        float                 fltVal;         // VT_R4
14        double                dblVal;         // VT_R8
15        VARIANT_BOOL          boolVal;        // VT_BOOL
16        SCODE                 scode;          // VT_ERROR
17        CY                    cyVal;          // VT_CY
18        DATE                  date;           // VT_DATE
19        BSTR                  bstrVal;        // VT_BSTR
20        IUnknown         FAR* punkVal;        // VT_UNKNOWN
21        IDispatch        FAR* pdispVal;       // VT_DISPATCH
22        SAFEARRAY        FAR* parray;         // VT_ARRAY|*
23        unsigned char    FAR* pbVal;          // VT_BYREF|VT_UI1
24        short            FAR* piVal;          // VT_BYREF|VT_I2
25        long             FAR* plVal;          // VT_BYREF|VT_I4
26        float            FAR* pfltVal;        // VT_BYREF|VT_R4
27        double           FAR* pdblVal;        // VT_BYREF|VT_R8
28        VARIANT_BOOL     FAR* pboolVal;       // VT_BYREF|VT_BOOL
29        SCODE            FAR* pscode;         // VT_BYREF|VT_ERROR
30        CY               FAR* pcyVal;         // VT_BYREF|VT_CY
31        DATE             FAR* pdate;          // VT_BYREF|VT_DATE
32        BSTR             FAR* pbstrVal;       // VT_BYREF|VT_BSTR
33        IUnknown FAR*    FAR* ppunkVal;       // VT_BYREF|VT_UNKNOWN
34        IDispatch FAR*   FAR* ppdispVal;      // VT_BYREF|VT_DISPATCH
35        SAFEARRAY FAR*   FAR* pparray;        // VT_ARRAY|*
36        VARIANT          FAR* pvarVal;        // VT_BYREF|VT_VARIANT
37        void             FAR* byref;          // Generic ByRef
38    };
39};

You initialize the VARIANT structure by storing a value in one of the fields of the tagged union and then storing the corresponding type code for the value in the vt member of the VARIANT structure. The VARIANT data type has a number of semantics that make using it tedious and error prone for C++ developers.

Correctly using a VARIANT requires that you remember the following rules:

  • A VARIANT must be initialized before use by calling the VariantInit function on it. Alternatively, you can initialize the type and associated value field to a valid state (such as setting the vt field to VT_EMPTY).

  • A VARIANT must be copied by calling the VariantCopy function on it. This performs the proper shallow or deep copy, as appropriate for the data type stored in the VARIANT.

  • A VARIANT must be destroyed by calling the VariantClear function on it. This performs the proper shallow or deep destroy, as appropriate for the data type stored in the VARIANT. For example, when you destroy a VARIANT containing a SAFEARRAY of BSTR s, VariantClear frees each BSTR element in the array and then frees the array itself.

  • A VARIANT can optionally represent, at most, one level of indirection, which is specified by adding the VT_BYREF bit setting to the type code. You can call VariantCopyInd to remove a single level of indirection from a VARIANT.

  • You can attempt to change the data type of a VARIANT by calling VariantChangeType[Ex].

With all these special semantics, it is useful to encapsulate these details in a reusable class. ATL provides such a class: CComVariant.

The CComVariant Class

The CComVariant class is an ATL utility class that is a useful encapsulation for the COM self-describing data type, VARIANT. The atlcomcli.h file contains the definition of the CComVariant class. The only state the class maintains is an instance of the VARIANT structure, which the class obtains by inheritance from the tagVARIANT structure. Conveniently, this means that a CComVariant instance is a VARIANT structure, so you can pass a CComVariant instance to any function that expects a VARIANT structure.

1class CComVariant: public tagVARIANT {
2    ...
3};

You often use a CComVariant instance when you need to pass a VARIANT argument to a COM method. The following code passes a string argument to a method that expects a VARIANT. The code uses the CComVariant class to do the conversion from a C-style string to the required VARIANT:

 1STDMETHODIMP put_Name(/* [in] */ const VARIANT* name);
 2
 3HRESULT SetName (LPCTSTR pszName) {
 4    // Initializes the VARIANT structure
 5    // Allocates a BSTR copy of pszName
 6    // Sets the VARIANT to the BSTR
 7    CComVariant v (pszName);
 8
 9    // Pass the raw VARIANT to the method
10    return pObj->put_Name(&v);
11
12    // Destructor clears v freeing the BSTR
13}

Constructors and Destructor

Twenty-three constructors are available for CComVariant objects. The default constructor simply invokes the COM function VariantInit, which sets the vt flag to VT_EMPTY and properly initializes the VARIANT so that it is ready to use. The destructor calls the Clear member function to release any resources potentially held in the VARIANT.

1CComVariant() {
2    ::VariantInit(this);
3}
4
5~CComVariant() {
6    Clear();
7}

The other 22 constructors initialize the VARIANT structure appropriately, based on the type of the constructor argument.

Many of the constructors simply set the vt member of the VARIANT structure to the value representing the type of the constructor argument, and store the value of the argument in the appropriate member of the union.

 1CComVariant(BYTE nSrc) {
 2    vt = VT_UI1;
 3    bVal = nSrc;
 4}
 5CComVariant(short nSrc) {
 6    vt = VT_I2;
 7    iVal = nSrc;
 8}
 9CComVariant(long nSrc, VARTYPE vtSrc = VT_I4) {
10    ATLASSERT(vtSrc == VT_I4 || vtSrc == VT_ERROR);
11    vt = vtSrc;
12    lVal = nSrc;
13}
14CComVariant( float fltSrc) {
15    vt = VT_R4;
16    fltVal = fltSrc;
17}

A few of the constructors are more complex. An SCODE looks like a long to the compiler. Therefore, constructing a CComVariant that specifies an SCODE or a long initialization value invokes the constructor that accepts a long. To enable you to distinguish these two cases, this constructor also takes an optional argument that allows you to specify whether the long should be placed in the VARIANT as a long or as an SCODE. When you specify a variant type other than VT_I4 or VT_ERROR, this constructor asserts in a debug build.

Windows 16-bit COM defined an HRESULT (a handle to a result code) as a data type that contained an SCODE (a status code). Therefore, you’ll occasionally see older legacy code that considers the two data types to be different. In fact, some obsolete macros convert an SCODE to an HRESULT and extract the SCODE from an HRESULT. However, the SCODE and HRESULT data types are identical in 32-bit COM applications. The VARIANT data structure contains an SCODE field instead of an HRESULT because that’s the way it was originally declared:

1CComVariant(long nSrc, VARTYPE vtSrc = VT_I4) ;

The constructor that accepts a bool initialization value sets the contents of the VARIANT to VARIANT_TRUE or VARIANT_FALSE, as appropriate, not to the bool value specified. A logical TRUE value, as represented in a VARIANT, must be VARIANT_TRUE (1 as a 16-bit value), and logical FALSE is VARIANT_FALSE (0 as a 16-bit value). The Microsoft C++ compiler defines the bool data type as an 8-bit (0 or 1) value. This constructor provides the conversion between the two representations of a Boolean value:

1CComVariant(bool bSrc) {
2    vt = VT_BOOL;
3    boolVal = bSrc ? ATL_VARIANT_TRUE : ATL_VARIANT_FALSE;
4}

Two constructors accept an interface pointer as an initialization value and produce a CComVariant instance that contains an AddRef’ed copy of the interface pointer. The first constructor accepts an IDispatch* argument. The second accepts an IUnknown* argument.

1CComVariant(IDispatch* pSrc) ;
2CComVariant(IUnknown* pSrc) ;

Two constructors enable you to initialize a CComVariant instance from another VARIANT structure or CComVariant instance:

1CComVariant(const VARIANT& varSrc) {
2    vt = VT_EMPTY; InternalCopy (&varSrc);
3}
4CComVariant(const CComVariant& varSrc); { /* Same as above */ }

Both have identical implementations, which brings up a subtle side effect, so let’s look at the InternalCopy helper function both constructors use:

 1void InternalCopy(const VARIANT* pSrc) {
 2    HRESULT hr = Copy(pSrc);
 3    if (FAILED(hr)) {
 4        vt = VT_ERROR;
 5        scode = hr;
 6#ifndef _ATL_NO_VARIANT_THROW
 7        AtlThrow(hr);
 8#endif
 9    }
10}

Notice how InternalCopy attempts to copy the specified VARIANT into the instance being constructed; when the copy fails, InternalCopy initializes the CComVariant instance as holding an error code (VT_ERROR). The Copy method used to attempt the copy returns the actual error code. This seems like an odd approach until you realize that a constructor cannot return an error, short of throwing an exception. ATL doesn’t require support for exceptions, so this constructor must initialize the instance even when the Copy method fails. You can enable exceptions in this code via the _ATL_NO_VARIANT_THROW macro shown in the code.

I once had a CComVariant instance that always seemed to have a VT_ERROR code in it, even when I thought it shouldn’t. As it turned out, I was constructing the CComVariant instance from an uninitialized VARIANT structure that resided on the stack. Watch out for code like this:

1void func () {       // The following code is incorrect
2  VARIANT v;         // Uninitialized stack garbage in vt member
3  CComVariant sv(v); // Indeterminate state
4}

Three constructors accept a string initialization value and produce a CComVariant instance that contains a BSTR. The first constructor accepts a CComBSTR argument and creates a CComVariant that contains a BSTR copied from the CComBSTR argument. The second accepts an LPCOLESTR argument and creates a CComVariant that contains a BSTR that is a copy of the specified string of OLECHAR characters. The third accepts an LPCSTR argument and creates a CComVariant that contains a BSTR that is a converted-to-OLECHAR copy of the specified ANSI character string. These three constructors also possibly can “fail.” In all three constructors, when the constructor cannot allocate memory for the new BSTR, it initializes the CComVariant instance to VT_ERROR with SCODE E_OUTOFMEMORY. The constructors also throw exceptions if the project settings are set appropriately.

1CComVariant(const CComBSTR& bstrSrc);
2CComVariant(LPCOLESTR lpszSrc);
3CComVariant(LPCSTR lpszSrc);

Although both the first and second constructors logically accept the same fundamental data type (a string of OLECHAR), only the first constructor properly handles BSTR s that contain an embedded NUL and only then if the CComBSTR object supplied has been properly constructed so that it has properly handled embedded NUL s. The following example makes this clear:

 1LPCOLESTR osz = OLESTR("This is a\0BSTR string");
 2BSTR bstrIn = ::SysAllocStringLen(osz, 21) ;
 3// bstrIn contains "This is a\0BSTR string"
 4
 5CComBSTR bstr(::SysStringLen(bstrIn), bstrIn);
 6
 7CComVariant v1(bstr); // Correct  v1 contains
 8                      // "This is a\0BSTR string"
 9CComVariant v2(osz);  // Wrong!  v2 contains "This is a"
10
11::SysFreeString (bstrIn) ;

Assignment

The CComVariant class defines an army of assignment operators: 33 in all. All the assignment operators do the following actions:

  • Clear the variant of its current contents

  • Set the vt member of the VARIANT structure to the value representing the type of the assignment operator argument

  • Store the value of the argument in the appropriate member of the union

 1CComVariant& operator=(char cSrc);           // VT_I1
 2CComVariant& operator=(int nSrc);            // VT_I4
 3CComVariant& operator=(unsigned int nSrc);   // VT_UI4
 4CComVariant& operator=(BYTE nSrc);           // VT_UI1
 5CComVariant& operator=(short nSrc);          // VT_I2
 6CComVariant& operator=(unsigned short nSrc); // VT_UI2
 7CComVariant& operator=(long nSrc);           // VT_I4
 8CComVariant& operator=(unsigned long nSrc);  // VT_UI4
 9
10CComVariant& operator=(LONGLONG nSrc);       // VT_I8
11CComVariant& operator=(ULONGULONG nSrc);     // VT_UI8
12CComVariant& operator=(float fltSrc);        // VT_R4
13CComVariant& operator=(double dblSrc);       // VT_R8
14CComVariant& operator=(CY cySrc);            // VT_CY

Other overloads accept a pointer to all the previous types. Internally, these produce a vt value that is the bitwise OR of VT_BYREF and the vt member listed earlier. For example, operator=(long*) sets vt to VT_I4 | VT_BYREF.

The remaining operator= methods have additional semantics. Like the equivalent constructor, the assignment operator that accepts a bool initialization value sets the contents of the VARIANT to VARIANT_TRUE or VARIANT_FALSE, as appropriate, not to the bool value specified.

1CComVariant& operator=(bool bSrc) ;

Two assignment operators accept an interface pointer and produce a CComVariant instance that contains an AddRef’ed copy of the interface pointer. One assignment operator accepts an IDispatch* argument. Another accepts an IUnknown* argument.

1CComVariant& operator=(IDispatch* pSrc) ;
2CComVariant& operator=(IUnknown* pSrc) ;

Two assignment operators allow you to initialize a CComVariant instance from another VARIANT structure or CComVariant instance. They both use the InternalCopy method, described previously, to make a copy of the provided argument. Therefore, these assignments can “fail” and produce an instance initialized to the VT_ERROR type (and possibly an exception).

1CComVariant& operator=(const CComVariant& varSrc);
2CComVariant& operator=(const VARIANT& varSrc);

One version of operator= accepts a SAFEARRAY as an argument:

1CComVariant& operator=(const SAFEARRAY *pSrc);

This method uses the COM API function SafeArrayCopy to produce an independent duplicate of the SAFEARRAY passed in. It properly sets the vt member to be the bitwise OR of VT_ARRAY and the vt member of the elements of pSrc.

The remaining three assignment operators accept a string initialization value and produce a CComVariant instance that contains a BSTR. The first assignment operator accepts a CComBSTR argument and creates a CComVariant that contains a BSTR that is a copy of the specified CComBSTR’s string data. The second accepts an LPCOLESTR argument and creates a CComVariant that contains a BSTR that is a copy of the specified string of OLECHAR characters. The third accepts an LPCSTR argument and creates a CComVariant that contains a BSTR that is a converted-to- OLECHAR copy of the specified ANSI character string.

1CComVariant& operator=(const CComBSTR& bstrSrc);
2CComVariant& operator=(LPCOLESTR lpszSrc);
3CComVariant& operator=(LPCSTR lpszSrc);

The previous remarks about the constructors with a string initialization value apply equally to the assignment operators with a string initialization value. In fact, the constructors actually use these assignment operators to perform their initialization.

One final option to set the value of a CComVariant is the SetByRef method:

1template< typename T >
2void SetByRef( T* pT ) {
3    Clear();
4    vt = CVarTypeInfo< T >::VT|VT_BYREF;
5    byref = pT;
6}

This method clears the current contents and then sets the vt field to the appropriate type with the addition of the VT_BYREF flag; this indicates that the variant contains a pointer to the actual data, not the data itself. The CVarTypeInfo class is another traits class. The generic version follows:

1template< typename T >
2class CVarTypeInfo {
3// VARTYPE corresponding to type T
4//    static const VARTYPE VT;
5// Pointer-to-member of corresponding field in VARIANT struct
6//    static T VARIANT::* const pmField;
7};

The comments indicate what each template specialization does: provides the VT constant that gives the appropriate VARTYPE value to use for this type and the pmField pointer that indicates which field of a variant stores this type. These are two of the specializations:

 1template<>
 2class CVarTypeInfo< unsigned char > {
 3public:
 4    static const VARTYPE VT = VT_UI1;
 5    static unsigned char VARIANT::* const pmField;
 6};
 7
 8__declspec( selectany ) unsigned char VARIANT::* const
 9    CVarTypeInfo< unsigned char >::pmField = &VARIANT::bVal;
10
11template<>
12class CVarTypeInfo< BSTR > {
13public:
14    static const VARTYPE VT = VT_BSTR;
15    static BSTR VARIANT::* const pmField;
16};
17__declspec( selectany ) BSTR VARIANT::* const
18    CVarTypeInfo< BSTR >::pmField = &VARIANT::bstrVal;

The initializers are necessary to set the pmField pointers appropriately. The pmField value is not currently used anywhere in ATL 8, but it could be useful for writing your own code that deals with VARIANT s.

CComVariant Operations

It’s important to realize that a VARIANT is a resource that must be managed properly. Just as memory from the heap must be allocated and freed, a VARIANT must be initialized and cleared. Just as the ownership of a memory block must be explicitly managed so it’s freed only once, the ownership of the contents of a VARIANT must be explicitly managed so it’s cleared only once. Four methods give you control over any resources a CComVariant instance owns.

The Clear method releases any resources the instance contains by calling the VariantClear function. For an instance that contains, for example, a long or a similar scalar value, this method does nothing except set the variant type field to VT_EMPTY. However, for an instance that contains a BSTR, the method releases the string. For an instance that contains an interface pointer, the method releases the interface pointer. For an instance that contains a SAFEARRAY, this method iterates over each element in the array, releasing each element, and then releases the SAFEARRAY itself.

1HRESULT Clear() { return ::VariantClear(this); }

When you no longer need the resource contained in a CComVariant instance, you should call the Clear method to release it. The destructor does this automatically for you. This is one of the major advantages of using CComVariant instead of a raw VARIANT: automatic cleanup at the end of a scope. So, if an instance will quickly go out of scope when you’re finished using its resources, let the destructor take care of the cleanup. However, a CComVariant instance as a global or static variable doesn’t leave scope for a potentially long time. The Clear method is useful in this case.

The Copy method makes a unique copy of the specified VARIANT. The Copy method produces a CComVariant instance that has a lifetime that is separate from the lifetime of the VARIANT that it copies.

1HRESULT Copy(const VARIANT* pSrc)
2       { return ::VariantCopy(this, const_cast<VARIANT*>(pSrc)); }

Often, you’ll use the Copy method to copy a VARIANT that you receive as an [in] parameter. The caller providing an [in] parameter is loaning the resource to you. When you want to hold the parameter longer than the scope of the method call, you must copy the VARIANT:

1STDMETHODIMP SomeClass::put_Option (const VARIANT* pOption) {
2    // Option saved in member m_Option of type CComVariant
3    return m_varOption.Copy (pOption) ;
4}

When you want to transfer ownership of the resources in a CComVariant instance from the instance to a VARIANT structure, use the Detach method. It clears the destination VARIANT structure, does a memcpy of the CComVariant instance into the specified VARIANT structure, and then sets the instance to VT_EMPTY. Note that this technique avoids extraneous memory allocations and AddRef / Release calls.

1HRESULT Detach(VARIANT* pDest);

Do not use the Detach method to update an [out] VARIANT argument without special care! An [out] parameter is uninitialized on input to a method. The Detach method clears the specified VARIANT before overwriting it. Clearing a VARIANT filled with random bits produces random behavior.

1STDMETHODIMP SomeClass::get_Option (VARIANT* pOption) {
2    CComVariant varOption ;
3    ... Initialize the variant with the output data
4
5    // Wrong! The following code can generate an exception,
6    // corrupt your heap, and give at least seven years bad luck!
7    return varOption.Detach (pOption);
8}

Before detaching into an [out] VARIANT argument, be sure to initialize the output argument:

1// Special care taken to initialize [out] VARIANT
2::VariantInit (pOption) ;
3// or
4pOption->vt = VT_EMPTY ;
5
6return vOption.Detach (pOption); // Now we can Detach safely.

When you want to transfer ownership of the resources in a VARIANT structure from the structure to a CComVariant instance, use the Attach method. It clears the current instance, does a memcpy of the specified VARIANT into the current instance, and then sets the specified VARIANT to VT_EMPTY. Note that this technique avoids extraneous memory allocations and AddRef / Release calls.

1HRESULT Attach(VARIANT* pSrc);

Client code can use the Attach method to assume ownership of a VARIANT that it receives as an [out] parameter. The function providing an [out] parameter transfers ownership of the resource to the caller.

1STDMETHODIMP SomeClass::get_Option (VARIANT* pOption);
2
3void VerboseGetOption () {
4    VARIANT v;
5    pObj->get_Option (&v) ;
6
7    CComVariant cv;
8    cv.Attach (&v);   // Destructor now releases the VARIANT
9}

Somewhat more efficiently, but potentially more dangerously, you could code this differently:

1void FragileGetOption() {
2    CComVariant v;          // This is fragile code!!
3    pObj->get_Option (&v) ; // Directly update the contained
4                            // VARIANT. Destructor now releases
5                            // the VARIANT.
6}

Note that, in this case, the get_Option method overwrites the VARIANT structure contained in the CComVariant instance. Because the method expects an [out] parameter, the get_Option method does not release any resources contained in the provided argument. In the preceding example, the instance was freshly constructed, so it is empty when overwritten. The following code, however, causes a memory leak:

1void LeakyGetOption() {
2    CComVariant v (OLESTR ("This string leaks!")) ;
3    pObj->get_Option (&v) ; // Directly updates the contained
4                            // VARIANT. Destructor now releases
5                            // the VARIANT.
6}

When you use a CComVariant instance as an [out] parameter to a method that expects a VARIANT, you must first clear the instance if there is any possibility that the instance is not empty.

1void NiceGetOption() {
2    CComVariant v (OLESTR ("This string doesn't leak!")) ;
3    ...
4    v.Clear ();
5    pObj->get_Option (&v) ; // Directly updates the contained
6                            // VARIANT. Destructor now releases
7                            // the VARIANT.
8
9}

The ChangeType method converts a CComVariant instance to the new type specified by the vtNew parameter. When you specify a second argument, ChangeType uses it as the source for the conversion. Otherwise, ChangeType uses the CComVariant instance as the source for the conversion and performs the conversion in place.

1HRESULT ChangeType(VARTYPE vtNew, const VARIANT* pSrc = NULL);

ChangeType converts between the fundamental types (including numeric-to-string and string-to-numeric coercions). ChangeType coerces a source that contains a reference to a type (that is, the VT_BYREF bit is set) to a value by retrieving the referenced value. ChangeType always coerces an object reference to a value by retrieving the object’s Value property. This is the property with the DISPID_VALUE DISPID. The ChangeType method can be useful not only for COM programming, but also as a general data type conversion library.

ComVariant Comparison Operators

The operator==() method compares a CComVariant instance for equality with the specified VARIANT structure:

1bool operator==(const VARIANT& varSrc) const ;
2bool operator!=(const VARIANT& varSrc) const ;

When the two operands have differing types, the operator returns false. If they are of the same type, the implementation calls the VarCmp API function [1] to do the proper comparison based on the underlying type. The operator!=() method returns the negation of the operator==() method.

Both the operator<() and operator>() methods perform their respective comparisons using the Variant Math API function VarCmp:

1bool operator<(const VARIANT& varSrc) const ;
2bool operator>(const VARIANT& varSrc) const ;

CComVariant Persistence Support

You can use the last three methods of the CComVariant class to read and write a VARIANT to and from a stream:

1HRESULT WriteToStream(IStream* pStream);
2HRESULT ReadFromStream(IStream* pStream);
3ULONG GetSize() const;

The WriteToStream method writes the vt type code to the stream. For simple types such as VT_I4, VT_R8, and similar scalar values, it writes the value of the VARIANT to the stream immediately following the type code. For an interface pointer, WriteToStream writes the GUID CLSID_NULL to the stream when the pointer is NULL. When the interface pointer is not NULL, WriteToStream queries the referenced object for its IPersistStream interface. If that fails, it queries for IPersistStreamInit. When the object supports one of these interfaces, WriteToStream calls the COM OleSaveToStream function to save the object to the stream. When the interface pointer is not NULL and the object does not support either the IPersistStream or IPersistStreamInit interfaces, WriteToStream fails.

For complex types, including VT_BSTR, all by-reference types, and all arrays, WriteToStream attempts to convert the value, if necessary, to a BSTR and writes the string to the stream using CComBSTR::WriteToStream.

The ReadFromStream method performs the inverse operation. First, it clears the current CComVariant instance. Then it reads the variant type code from the stream. For the simple types, such as VT_I4, VT_R8, and similar scalar values, it reads the value of the VARIANT from the stream. For an interface pointer, it calls the COM OleLoadFromStream function to read the object from the stream, requesting the IUnknown or IDispatch interface, as appropriate. When OleLoadFromStream returns REGDB_E_CLASSNOTREG (usually because of reading CLSID_NULL), ReadFromStream silently returns an S_OK status.

For all other types, including VT_BSTR, all by-reference types, and all arrays, ReadFromStream calls CComBSTR::ReadFromStream to read the previously written string from the stream. The method then coerces the string back to the original type.

GetSize provides an estimate of the amount of space that the variant will require in the stream. This is needed for implementing various methods on the persistence interfaces. The size estimate is exactly correct for simple types (int, long, double, and so on). For variants that contain interface pointers, CComVariant does a QueryInterface for the IPersistStream or IPersistStreamInit interfaces and uses the GetSizeMax( ) method to figure out the size. For other types, the variant is converted to a BSTR and the length of the string is used.

In general, the GetSize( ) method is used as an initial estimate for things such as buffer sizes. When the code inside ATL calls GetSize( ) (mainly in persistence support, discussed in Chapter 7, “Persistence in ATL”) it correctly grows the buffers if the estimate is low. In your own use, be aware that sometimes the GetSize( ) method won’t be exactly correct.

The CComSafeArray Smart SAFEARRAY Class

A Review of the COM SAFEARRAY Data Type

IDL provides several attributes for specifying arrays in COM interfaces. Attributes such as [size_is] and [length_is] enable you to adorn method definitions with information required to marshal these arrays across COM boundaries. Yet not all languages support arrays in the same way. For instance, some languages support zero-based arrays, while others require one-based arrays. Still others, such as Visual Basic, allow the application itself to decide whether the arrays it references are zero-based or one-based. Array storage varies from language to language as well: Some languages store elements in row-major order, and others use column-major order. To make the situation even worse, type libraries don’t support the IDL attributes needed to marshal C-style IDL arrays; the MIDL compiler silently drops these attributes when generating type libraries.

To address the challenges of passing arrays between COM clients in a language-agnostic manner, Automation defines the SAFEARRAY data type. In much the same way as VARIANT s are self-describing generic data types, SAFEARRAY s are self-describing generic arrays. SAFEARRAY s are declared in IDL as follows:

1interface IMyInterface : IUnknown {
2    HRESULT GetArray([out,retval]
3        SAFEARRAY(VARIANT_BOOL)* myArray);
4};

The VARIANT_BOOL parameter to the SAFEARRAY declaration indicates the data type of the elements in the SAFEARRAY. This type must be an Automation-compatible type as well, meaning that it must be one of the data types that can be contained in a VARIANT. The MIDL compiler preserves this information in the type library so that clients can discover the underlying type of the SAFEARRAY.

The C++ binding for the SAFEARRAY type is actually a struct that represents a self-describing array. It contains a description of the contents of the array, including the upper and lower bounds and the total number of elements in the array. The SAFEARRAY struct is defined in oaidl.h as follows:

1typedef struct tagSAFEARRAY {
2    USHORT cDims;
3    USHORT fFeatures;
4    ULONG cbElements;
5    ULONG cLocks;
6    PVOID pvData;
7    SAFEARRAYBOUND rgsabound[ 1 ];
8} SAFEARRAY;

The upper and lower bounds for the SAFEARRAY are stored in the rgsabound array. Each element in this array is a SAFEARRAYBOUND structure.

1typedef struct tagSAFEARRAYBOUND {
2    ULONG cElements;
3    LONG lLbound;
4} SAFEARRAYBOUND;

The leftmost dimension of the array is contained in rgsabound[0], and the rightmost dimension is in rgsabound[cDims - 1]. For example, an array declared with C-style syntax to have dimensions of [3][4] would have two elements in the rgsabound array. The first element at offset zero would have a cElements value of 3 and an lLbound value of 0; the second element at offset one would have a cElements value of 4 and also an lLbound value of 0.

The pvData field of the SAFEARRAY struct points to the actual data in the array. The cbElements array indicates the size of each element. As you can see, this data type is flexible enough to represent an array with an arbitrary number of elements and dimensions.

COM provides a number of APIs for managing SAFEARRAY s. These functions enable you to create, access, and destroy SAFEARRAY s of various dimensions and sizes. The following code demonstrates how to use these functions to manipulate two-dimensional SAFEARRAY s of double. The first step is to create an array of SAFEARRAYBOUND structures to indicate the number and size of the array dimensions:

1SAFEARRAYBOUND rgsabound[2];
2rgsabound[0].cElements = 3;
3rgsabound[0].lLbound = 0;
4rgsabound[1].cElements = 4;
5rgsabound[1].lLbound = 0;

This code specifies a two-dimensional array with three elements in the first dimension (three rows) and four elements in the second dimension (four columns). This array is then passed to the SafeArrayCreate function to allocate the appropriate amount of storage:

1SAFEARRAY* psa = ::SafeArrayCreate(VT_R8, 2, rgsabound);

The first parameter to this function indicates the data type for the elements of the array. The second parameter specifies the number of elements in the rgsabound array (for example, the number of dimensions). The final parameter is the array of SAFEARRAYBOUND structures describing each dimension of the SAFEARRAY. You can retrieve elements of the SAFEARRAY using the SafeArrayGetElement function, like this:

1long rgIndices[] = { 2, 1 };
2double lElem;
3::SafeArrayGetElement(psa, rgIndices, (void*)&lElem);

This code retrieves the element stored at location [1][2] – that is, the second row, third column. Confusingly, the rgIndices specifies the SAFEARRAY indices in reverse order: The first element of the rgIndices array specifies the rightmost dimension of the SAFEARRAY. You must manually free the SAFEARRAY and the data it contains using the SafeArrayDestroy function.

1::SafeArrayDestroy(psa);

As you can see, manipulating SAFEARRAY s with these APIs is a bit tedious. Fortunately, ATL provides some relief in the form of a templatized wrapper class called CComSafeArray. This class is defined in atlsafe.h as follows:

1template <typename T,
2    VARTYPE _vartype = _ATL_AutomationType<T>::type>
3class CComSafeArray {
4  ...
5public:
6     LPSAFEARRAY m_psa;
7}

This template class encapsulates a pointer to a SAFEARRAY as its only state. The first template parameter is the C++ type that will be stored in the internal SAFEARRAY. Recall that SAFEARRAY s can hold only Automation-compatible types as elements; that is, data types that can be stored in a VARIANT. So, the second template parameter to CComSafeArray indicates the VARTYPE of the elements to be stored. Only a subset of the VARIANT-compatible types are supported with CComSafeArray. These are listed in Table 3.1.

Table 3.1. ARTYPEs CComSafeArray Supports

VARTYPE

C++ Type

VT_I1

char

VT_I2

short

VT_I4

int

VT_I4

long

VT_I8

longlong

VT_UI1

byte

VT_UI2

ushort

VT_UI4

uint

VT_UI4

ulong

VT_UI8

ulonglong

VT_R4

float

VT_R8

double

VT_DECIMAL

decimal

VT_VARIANT

variant

VT_CY

currency

VT_BSTR

BSTR

VT_DISPATCH

IDispatch pointer

VT_UNKNOWN

IUnknown pointer

The documentation indicates that the last three VARTYPEsBSTR, IDispatch, and IUnknown pointers – are not supported. The documentation is wrong; CComSafeArray uses template specialization to accommodate the unique semantics of these data types. More on this comes later in this section.

The default value for the second template parameter employs a clever combination of templates and macros to automatically associate the C++ data type with the VARTYPE that the SAFEARRAY API functions must use internally. The default parameter value uses the _ATL_AutomationType dummy template, defined as follows:

1template <typename T>
2struct _ATL_AutomationType { };

The DEFINE_AUTOMATION_TYPE_FUNCTION macro generates type mappings from the C++ data type to the appropriate VARTYPE. The type enum member holds the VARTYPE that CComSafeArray ultimately will use:

 1#define DEFINE_AUTOMATION_TYPE_FUNCTION(ctype,
 2 typewrapper, oleautomationtype) \
 3    template <> \
 4    struct _ATL_AutomationType<ctype> { \
 5    typedef typewrapper _typewrapper;\
 6    enum { type = oleautomationtype }; \
 7    static void* GetT(const T& t) { \
 8        return (void*)&t; \
 9    } \
10};

A series of these macros are declared in atlsafe.h to map CComSafeArray-supported types to the appropriate VARTYPE. Note that these macros include as the second macro parameter a typewrapper. This is interesting only for the four supported data types that require special handling: VARIANT, BSTR, IDispatch *, and IUnknown *.

 1DEFINE_AUTOMATION_TYPE_FUNCTION(CHAR, CHAR, VT_I1)
 2DEFINE_AUTOMATION_TYPE_FUNCTION(SHORT, SHORT, VT_I2)
 3DEFINE_AUTOMATION_TYPE_FUNCTION(INT, INT, VT_I4)
 4DEFINE_AUTOMATION_TYPE_FUNCTION(LONG, LONG, VT_I4)
 5DEFINE_AUTOMATION_TYPE_FUNCTION(LONGLONG, LONGLONG, VT_I8)
 6DEFINE_AUTOMATION_TYPE_FUNCTION(BYTE, BYTE, VT_UI1)
 7DEFINE_AUTOMATION_TYPE_FUNCTION(USHORT, USHORT, VT_UI2)
 8DEFINE_AUTOMATION_TYPE_FUNCTION(UINT, UINT, VT_UI4)
 9DEFINE_AUTOMATION_TYPE_FUNCTION(ULONG, ULONG, VT_UI4)
10DEFINE_AUTOMATION_TYPE_FUNCTION(ULONGLONG, ULONGLONG, VT_UI8)
11DEFINE_AUTOMATION_TYPE_FUNCTION(FLOAT, FLOAT, VT_R4)
12DEFINE_AUTOMATION_TYPE_FUNCTION(DOUBLE, DOUBLE, VT_R8)
13DEFINE_AUTOMATION_TYPE_FUNCTION(DECIMAL, DECIMAL, VT_DECIMAL)
14DEFINE_AUTOMATION_TYPE_FUNCTION(VARIANT, CComVariant, VT_VARIANT)
15DEFINE_AUTOMATION_TYPE_FUNCTION(CY, CY, VT_CY)

With these definitions in hand, declaring an instance of CComSafeArray<long> would generate a second parameter of _ATL_Automation_Type<long>::type, where the exposed type member is equal to VT_I4.

Constructors and Destructor

The template parameter you pass to CComSafeArray establishes only the data type of the SAFEARRAY elements, not the number of dimensions or the size of each dimension. This information is established through one of the seven CComSafeArray constructors. The first constructor is the default (parameterless) constructor and simply initializes m_psa to NULL. Three other constructors create a new CComSafeArray instance from dimension and size information. The first of these constructors creates a one-dimensional array with ulCount elements and is indexed starting with lLBound:

1explicit CComSafeArray(ULONG ulCount, LONG lLBound = 0);

Internally, this constructor uses these arguments to create an instance of a class that serves as a thin wrapper for the SAFEARRAYBOUND structure discussed earlier. The CComSafeArrayBound class exposes simple methods for manipulating the number of elements in a particular CComSafeArray dimension, as well as the starting index (lower bound) for that dimension. Note that this class derives directly from the SAFEARRAYBOUND structure, so it can be passed to methods that expect either a CComSafeArrayBound class or a SAFEARRAYBOUND structure.

 1class CComSafeArrayBound : public SAFEARRAYBOUND {
 2    CComSafeArrayBound(ULONG ulCount = 0, LONG lLowerBound = 0)
 3    { ... }
 4    CComSafeArrayBound&
 5    operator=(const CComSafeArrayBound& bound)
 6    { ... }
 7    CComSafeArrayBound& operator=(ULONG ulCount) { ... }
 8    ULONG GetCount() const  { ... }
 9    ULONG SetCount(ULONG ulCount)  { ... }
10    LONG GetLowerBound() const  { ... }
11    LONG SetLowerBound(LONG lLowerBound)  { ... }
12    LONG GetUpperBound() const  { ... }
13};

A quick look at the implementation for the CComSafeArray(ULONG, LONG) constructor demonstrates how all the nondefault constructors use the CComSafeArrayBound wrapper class:

1explicit CComSafeArray(ULONG ulCount, LONG lLBound = 0)
2    : m_psa(NULL) {
3    CComSafeArrayBound bound(ulCount, lLBound);
4    HRESULT hRes = Create(&bound);
5    if (FAILED(hRes))
6       AtlThrow(hRes);
7}

An instance of CComSafeArrayBound is created and passed to the Create member function, which is itself a thin wrapper over the SAFEARRAY API functions. As shown in the following code fragment, Create uses the SafeArrayCreate API to support building a SAFEARRAY with any number of dimensions:

 1HRESULT Create(const SAFEARRAYBOUND *pBound, UINT uDims = 1) {
 2    ATLASSERT(m_psa == NULL);
 3    ATLASSERT(uDims > 0);
 4    HRESULT hRes = S_OK;
 5    m_psa = SafeArrayCreate(_vartype, uDims,
 6    const_cast<LPSAFEARRAYBOUND>(pBound));
 7    if (NULL == m_psa)
 8        hRes = E_OUTOFMEMORY;
 9    else
10        hRes = Lock();
11    return hRes;
12}

This first constructor just shown is probably the most frequently used. One-dimensional SAFEARRAY s are much more common than multidimensional SAFEARRAY s, and C++ developers are accustomed to zero-based array indexing. You make use of this simple constructor with code such as the following:

1// create a 1-D zero-based SAFEARRAY of long with 10 elements
2CComSafeArray<long> sa(10);
3
4// create a 1-D one-based SAFEARRAY of double with 5 elements
5CComSafeArray<double> sa(5,1);

The second CComSafeArray constructor enables you to pass in a SAFEARRAYBOUND structure or a CComSafeArrayBound instance:

1explicit CComSafeArray(const SAFEARRAYBOUND& bound);

This constructor is invoked when you write code similar to the following:

1CComSafeArrayBound bound(5,1);    // 1-D one-based array
2CComSafeArray<long> sa(bound);

This constructor is arguably less useful and less succinct than passing the bounds information directly via the first constructor shown. You use the third constructor to create a multidimensional SAFEARRAY. This constructor accepts an array of SAFEARRAYBOUND structures or CSafeArrayBound instances, along with a UINT parameter to indicate the number of dimensions:

1explicit CComSafeArray(const SAFEARRAYBOUND *pBound, UINT uDims = 1);

You create a multidimensional CComSafeArray with this constructor as follows:

 1// 3-D array with all dimensions
 2// left-most dimension has 3 elements
 3CComSafeArrayBound bound1(3);
 4// middle dimension has 4 elements
 5CComSafeArrayBound bound2(4);
 6// right-most dimension has 5 elements
 7CComSafeArrayBound bound3(5);
 8
 9// equivalent C-style array indices would be [3][4][5]
10CComSafeArrayBound rgBounds[] = { bound1, bound2, bound3 };
11CComSafeArray<int> sa(rgBounds, 3);

Note that nothing prevents you from creating different starting indices for the different dimensions of the SAFEARRAY``nothing but your conscience that is. This would be extraordinarily confusing for any code that uses this type. In any event, as mentioned previously, multidimensional ``SAFEARRAY s are pretty rare creatures in reality, so we won’t belabor the point.

The remaining three CComSafeArray constructors create an instance from an existing SAFEARRAY or CComSafeArray. They are declared as follows:

1CComSafeArray(const SAFEARRAY *psaSrc) : m_psa(NULL);
2CComSafeArray(const SAFEARRAY& saSrc) : m_psa(NULL);
3CComSafeArray(const CComSafeArray& saSrc) : m_psa(NULL);

All three constructors do the same thing: check for a NULL source and delegate to the CopyFrom method to duplicate the contents of the source instance. CopyFrom accepts a SAFEARRAY* and CComSafeArray provides a SAFEARRAY* cast operator, so the third constructor delegates to the CopyFrom method as well. This produces a clone of the source array. The following code demonstrates how it instantiates a CComSafeArray from an existing instance:

1CComSafeArray<int> saSrc(5);    // source is 1-D array of 5 ints
2// allocate storage for 1-D array of 5 ints
3// and copy contents of source
4CComSafeArray<int> saDest(saSrc);

The destructor for CComSafeArray is quite simple as well. It automatically releases the resources allocated for the SAFEARRAY when the instance goes out of scope. The implementation simply delegates to the Destroy method, which is defined as follows:

 1HRESULT Destroy() {
 2    HRESULT hRes = S_OK;
 3    if (m_psa != NULL) {
 4        hRes = Unlock();
 5        if (SUCCEEDED(hRes)) {
 6            hRes = SafeArrayDestroy(m_psa);
 7            if (SUCCEEDED(hRes))
 8                m_psa = NULL;
 9        }
10    }
11    return hRes;
12}

The Destroy method first calls Unlock to decrement the lock count on the internal SAFEARRAY and then simply delegates to the SafeArrayDestroy method. The significance of lock counting SAFEARRAY s is discussed shortly.

Assignment

CComSafeArray defines two assignment operators. Both duplicate the contents of the right-side instance, clearing the contents of the left-side instance beforehand. These operators are defined as follows:

 1CComSafeArray<T>& operator=(const CComSafeArray& saSrc) {
 2    *this = saSrc.m_psa;
 3    return *this;
 4}
 5CComSafeArray<T>& operator=(const SAFEARRAY *psaSrc) {
 6    ATLASSERT(psaSrc != NULL);
 7HRESULT hRes = CopyFrom(psaSrc);
 8    if (FAILED(hRes))
 9        AtlThrow(hRes);
10    return *this;
11}

The assignment statement in the first line of the first operator delegates immediately to the second operator that accepts a SAFEARRAY* parameter. CopyFrom clears the contents of the destination SAFEARRAY by eventually calling SafeArrayDestroy to free resources allocated when the target SAFEARRAY was created. This code gets invoked with code such as the following:

1CComSafeArray<long> sa1(10);
2// do something interesting with sa1
3CComSafeArray<long> sa2(5);
4
5// free contents of sa1, duplicate contents
6// of sa2 and put into sa1
7sa1 = sa2;

The Detach and Attach Methods

As with the CComVariant and CComBSTR classes discussed earlier, the CComSafeArray class wraps a data type that must be carefully managed if resource leaks are to be avoided. The storage allocated for the encapsulated SAFEARRAY must be explicitly created and freed using the SAFEARRAY API functions. In fact, two chunks of memory must be managed: the SAFEARRAY structure itself and the actual data contained in the SAFEARRAY.

Just as with CComVariant and CComBSTR, the CComSafeArray class provides Attach and Detach methods to wrap a preallocated SAFEARRAY:

 1HRESULT Attach(const SAFEARRAY *psaSrc) {
 2    ATLENSURE_THROW(psaSrc != NULL, E_INVALIDARG);
 3
 4    VARTYPE vt;
 5    HRESULT hRes = ::ATL::AtlSafeArrayGetActualVartype(
 6        const_cast<LPSAFEARRAY>(psaSrc), &vt);
 7    ATLENSURE_SUCCEEDED(hRes);
 8    ATLENSURE_THROW(vt == GetType(), E_INVALIDARG);
 9    hRes = Destroy();
10    m_psa = const_cast<LPSAFEARRAY>(psaSrc);
11    hRes = Lock();
12    return hRes;
13}
14
15LPSAFEARRAY Detach() {
16    Unlock();
17    LPSAFEARRAY pTemp = m_psa;
18    m_psa = NULL;
19    return pTemp;
20}

The Attach operation first checks to see if the type contained in the SAFEARRAY being attached matches the type passed as a template parameter. If the type is correct, the method next releases its reference to the encapsulated SAFEARRAY by calling Destroy. We glossed over the Destroy method when we presented it previously, but you’ll note that the first thing Destroy did was call the CComSafeArray ‘s Unlock method. The lock count in the SAFEARRAY structure is an interesting historical leftover.

Back in the days of 16-bit Windows, the OS couldn’t rely on having a virtual memory manager. Every chunk of memory was dealt with as a direct physical pointer. To fit into that wonderful world of 640KB, memory management required an extra level of indirection. The GlobalAlloc API function that’s still with us is an example. When you allocate memory via GlobalAlloc, you don’t get an actual pointer back. Instead, you get an HGLOBAL. To get the actual pointer, you call GlobalLock and pass it the HGLOBAL. When you’re done working with the pointer, you call GlobalUnlock. This doesn’t actually free the memory; if you call GlobalLock again on the same HGLOBAL, your data will still be there, but on 16-bit Windows the pointer you got back could be different. While the block is unlocked, the OS is free to change the physical address where the block lives by copying the contents.

Today, of course, the virtual memory managers inside modern CPUs handle all this. Still, some vestiges of those old days remain. The SAFEARRAY is one of those vestiges. You are not allowed to do this to access a SAFEARRAY ‘s data:

1SAFEARRAY *psa = ::SafeArrayCreateVector(VT_I4, 0, 10);
2// BAD - this pointer may not be valid!
3int *pData = reinterpret_cast<int *>(pda->pvData);
4// BOOM (maybe)
5pData[0] = 5;

Instead, you need to first lock the SAFEARRAY:

1SAFEARRAY *psa = ::SafeArrayCreateVector(VT_I4, 0, 10);
2// GOOD - this will allocate the actual storage for the data
3::SafeArrayLock(psa);
4// Now the pointer is valid
5int *pData = ( int * )(pda->pvData);
6pData[0] = 5;
7// Unlock after we're done
8::SafeArrayUnlock( psa );

Locking the SAFEARRAY actually allocates the storage for the data if it doesn’t already exist and sets the pvData field of the SAFEARRAY structure. Several different APIs perform this function. You can’t just do psa->cLocks++; you must call an appropriate API function.

In the bad old days, it was important that handles got unlocked as quickly as possible; if they didn’t, the OS couldn’t move memory around and eventually everything ground to a halt as memory fragmentation grew. These days, there’s no need to worry about unlocking, but the API remains. So, the CComSafeArray takes a simple approach: It locks the data as soon as it gets the SAFEARRAY and doesn’t unlock it until the SAFEARRAY is either Destroyed or Detached.

You usually use Attach inside a method implementation to wrap a SAFEARRAY that has been passed to you:

 1STDMETHODIMP SomeClass::AverageArray(/* [in] */ SAFEARRAY* psa,
 2    /* [out] */ LONG* plAvg) {
 3    if (!plAvg) return E_POINTER;
 4    CComSafeArray<long> sa; // Note: no type check is done
 5                            // against psa type
 6    sa.Attach(psa);         // we're pointing at the same
 7                            // memory as psa
 8
 9    ... perform some calculations
10
11    sa.Detach(); // Must detach here or risk a crash
12    return S_OK;
13}

When you want to return a SAFERRAY from a method call, turn to the Detach operation, as in the following example:

 1STDMETHODIMP SomeClass::get_Array(/* [out] */ SAFEARRAY** ppsa) {
 2    if (!ppsa) return E_POINTER;
 3    CComSafeArray<long> sa(10);
 4
 5    ... populate sa instance
 6
 7    // no resources released when we leave scope
 8    // and no copying performed
 9    *ppsa = sa.Detach();
10    return S_OK;
11}

Attach and Detach don’t do any copying of the SAFEARRAY, and with the lock count in place, you might be tempted to think of the lock count as a kind of reference counting. Unfortunately, ATL 8 has a bug in the implementation of the Destroy method that makes this use of CComSafeArray problematic. Consider this code sample:

 1STDMETHODIMP SomeClass::DontDoThis(SAFEARRAY* psa) {
 2    // We have two references to the safearray
 3    CComSafeArray<long> sa1, sa2;
 4    sa1.Attach(psa);
 5    sa2.Attach(psa);
 6
 7    // manipulate the array here
 8    // BUG: Don't do this
 9    sa2.Destroy( );
10}

The explicit call to sa2.Destroy() will not actually destroy the underlying SAFEARRAY; this makes sense because there are still outstanding references (and locks) on the underlying data structure. It did, however, call Unlock(). Here’s the bug: Even though Destroy was called, sa2 thinks that it’s holding on to a valid reference to a SAFEARRAY. As a result, the destructor of sa2 calls Destroy() again, resulting in too many calls to Unlock(). The results afterward are potentially not pretty. To avoid this bug, when you’re using CComSafeArray, never Attach multiple CComSafeArray objects to the same SAFEARRAY pointer.

CComSafeArray Operations

Several methods are provided for retrieving information about the size and shape of a CComSafeArray instance:

1LONG GetLowerBound(UINT uDim = 0) const;
2LONG GetUpperBound(UINT uDim = 0) const;
3ULONG GetCount(UINT uDim = 0) const;
4UINT GetDimensions() const;
5VARTYPE GetType() const ;
6bool IsSizable() const;

All these methods are fairly simple and self-explanatory. GetLowerBound and GetUpperBound return the lower and upper bounds of a particular dimension of the SAFEARRAY. The GetCount method takes a specific dimension number and returns the number of elements in that dimension. GeTDimensions returns the total number of dimensions in the SAFEARRAY, also known as the array rank. You can query the Automation VARTYPE with the GetType method. IsSizable indicates whether the SAFEARRAY can be resized. Recall that the definition of the SAFEARRAY data type included an fFeatures bit field that stores information about how the array is allocated.

1typedef struct tagSAFEARRAY {
2    ...
3    USHORT fFeatures;
4    ...
5} SAFEARRAY;

The SAFEARRAY API functions use this information to properly release elements when the SAFEARRAY is destroyed. The FADF_FIXEDSIZE bit indicates whether the SAFEARRAY can be resized. By default, the SAFEARRAY created with an instance of CComSafeArray is resizable, so the IsSizable method returns TRUE. CComSafeArray doesn’t expose any methods for directly manipulating the fFeatures flags, so they would change from their default values only if you directly access the encapsulated SAFEARRAY, if the SAFEARRAY passed to the CComSafeArray constructor has different values, or if an Attach is performed on a SAFEARRAY that manipulated the fFeatures field. You can access the internal SAFEARRAY directly with the GetSafeArrayPtr method:

1LPSAFEARRAY* GetSafeArrayPtr() {
2    return &m_psa;
3}

If a CComSafeArray instance is resizable, clients can use two different Resize methods to grow or shrink a SAFEARRAY:

1HRESULT Resize(ULONG ulCount, LONG lLBound = 0);
2HRESULT Resize(const SAFEARRAYBOUND *pBound);

The first version takes the new number of elements and lower bound, constructs a SAFEARRAYBOUND structure from the supplied parameters, and delegates the real work to the second version that accepts a SAFEARRAYBOUND* parameter. This second version of Resize first verifies that the SAFEARRAY is resizable and then relies upon the SAFEARRAY API SafeArrayRedim to do the heavy lifting. The first thing to note is that only the least-significant (rightmost) dimension of a SAFEARRAY can be resized. So, you can change the size of a SAFEARRAY with dimensions [3][5][7] to one with dimensions [3][5][4], but you cannot change it to have dimensions [6][5][7]. If the resizing operation reduces the size of the SAFEARRAY, SafeArrayRedim deallocates the elements beyond the new bounds. If the operation increases the size of the SAFEARRAY, SafeArrayRedim allocates and initializes the appropriate number of new elements.

Be warned, there’s a nasty bug in the implementation of the Resize method:

 1HRESULT Resize(const SAFEARRAYBOUND *pBound)                              {
 2    ATLASSUME(m_psa != NULL);
 3    ATLASSERT(pBound != NULL);
 4    if (!IsSizable()) {
 5        return E_FAIL;
 6    }
 7    HRESULT hRes = Unlock();
 8    if (SUCCEEDED(hRes)) {
 9        hRes = SafeArrayRedim(m_psa, const_cast<LPSAFEARRAYBOUND>(pBound));
10        if (SUCCEEDED(hRes)) {
11            hRes = Lock();
12        }
13    }
14    return hRes;
15}

If the underlying call to SafeArrayRedim fails, the call to relock the SAFEARRAY is never made. When that happens, everything falls apart, and the destructor might even fail. If you call Resize, be very careful to check the return HRESULT. If it’s a failure, you really can’t assume anything about the state of the encapsulated SAFEARRAY. The best bet is to Detach it and clean up manually.

Microsoft has agreed that this is a bug but was unable to fix it in time for the Visual Studio 2005 release. Hopefully, it’ll be officially fixed soon.

CComSafeArray also provides three useful Add functions that you can use to append elements in a SAFEARRAY to the end of an existing CComSafeArray instance. Note that these methods work for only one-dimensional SAFEARRAY s. All three versions will assert if the Add method is invoked on a CComSafeArray instance that contains a multidimensional SAFEARRAY.

1HRESULT Add(const T& t, BOOL bCopy = TRUE);
2HRESULT Add(ULONG ulCount, const T *pT, BOOL bCopy = TRUE);
3HRESULT Add(const SAFEARRAY *psaSrc);

The first version of Add tacks on a single element to the end of the SAFEARRAY. If the SAFEARRAY is NULL, Add first invokes the Create method to allocate an empty SAFEARRY. Resize then increases the size of the SAFEARRAY by one, and the SetAt method is called to insert the value of the t parameter into the last element of the SAFEARRAY. The bCopy parameter is discussed further in a moment when we examine the CComSafeArray accessors: SetAt and GetAt. For now, simply understand that this parameter controls whether the CComSafeArray is appended with an independent duplicate of the new item or whether it actually takes ownership of the item. The second version of Add accepts a count and an array of items to append. It works similar to the single-element version: First, it creates an empty SAFEARRAY, if necessary; then, it calls Resize to grow the SAFEARRAY by ulCount elements. SetAt is invoked within a loop to initialize the new SAFEARRAY elements with the value of the items supplied in the pT parameter. Finally, the third version of Add accepts a pointer to a SAFEARRAY and appends all elements that it contains to the end of the CComSafeArray instance. This version of Add relies upon Resize and SetAt to do its work in exactly the same manner as do the other two versions. Here’s how you might use these methods in your own code:

1CComSafeArray<int> sa;  // sa::m_psa is NULL
2sa.Add(7);              // sa allocated and now contains { 7 }
3
4int rgVal[] = { 8, 9 };
5sa.Add(2, rgVal);       // sa now contains { 7, 8, 9 }
6
7sa.Add(sa);             // sa now contains { 7, 8, 9, 7, 8, 9 }
8                        // see discussion of cast operators to
9                        // understand what makes this line work

Warning: The various Add overloads call the Resize method under the hood, so they’re subject to the same buggy behavior if Resize fails.

CComSafeArray Element Accessors

CComSafeArray provides five methods for reading and writing individual elements of the encapsulated SAFEARRAY. Three of these methods are used for accessing one-dimensional SAFEARRAY s. The GetAt method comes in two flavors.

 1const typename _ATL_AutomationType<T>::_typewrapper&
 2GetAt(LONG lIndex) const {
 3    ATLASSUME(m_psa != NULL);
 4    if(m_psa == NULL)
 5        AtlThrow(E_FAIL);
 6    LONG lLBound = GetLowerBound();
 7    ATLASSERT(lIndex >= lLBound);
 8    ATLASSERT(lIndex <= GetUpperBound());
 9    if ((lIndex < lLBound) || (lIndex > GetUpperBound()))
10        AtlThrow(E_INVALIDARG);
11
12    return ( (_ATL_AutomationType<T>::_typewrapper*)
13        m_psa->pvData )[lIndex-lLBound];
14}
15
16_ATL_AutomationType<T>::_typewrapper& GetAt(LONG lIndex) {
17    // code identical to const version
18}

The two GetAt methods differ only in that the first version uses the const qualifier to enforce read-only semantics for the accessed element. The methods retrieve the upper and lower bounds and validate the specified index against these bounds. Note that the lIndex passed in is the index relative to the lLBound defined for the CComSafeArray instance, which might or might not be zero. To retrieve the requested element, the pvData field of the encapsulated SAFEARRAY is cast to the element type; conventional C-style pointer arithmetic does the rest.

At this point, it’s worth examining the significance of the _typewrapper field of the _ATL_AutomationType template class presented earlier. Recall that a series of DEFINE_AUTOMATION_TYPE_FUNCTION macros associated the supported C++ data types with both their corresponding Automation VARTYPE as well as a wrapper class. Only one DEFINE_AUTOMATION_TYPE_FUNCTION macro actually supplied a real wrapper class for the C++ data typeall the others shown so far simply use the actual C++ type as the wrapper type. The one macro mapped the VARIANT data type to the CComVariant wrapper class and to a VARTYPE value of VT_VARIANT. This internally sets _typewrapper to CComVariant and allows CComSafeArray to both leverage the convenient semantics of CComVariant in its internal implementation and return CComVariant elements of the SAFEARRAY to the client. Using a wrapper class in the typecasting code within GetAt relies upon the fact that CComVariant holds the encapsulated VARIANT as its only state, as discussed in the previous section on the CComVariant class. An instance of CComVariant resides at precisely the same memory address and occupies precisely the same storage as the encapsulated VARIANT. So, CComSafeArray can seamlessly deal in terms of the _typewrapper type internally and expose the wrapper type to the client as a convenience. Note that wrapper classes for element types must hold the encapsulated type as their only state if this scheme is to work correctly. You’ll see in a moment that the GetAt method isn’t the only method that breaks down if this isn’t the case.

The list of DEFINE_AUTOMATION_TYPE_FUNCTION macros didn’t generate type mappings support for two other important SAFEARRAY element types that CComSafeArray actually supports, even though the current ATL documentation doesn’t mention them. Instead of macros, CComSafeArray provides support for elements of type BSTR, IDispatch*, and IUnknown* through template specialization. The template specialization for all three data types looks very similar; we examine the one for BSTR as an example because you’re already familiar with the associated wrapper class: CComBSTR. The wrapper class for IDispatch* and IUnknown* is CComPtr, presented in detail in the later section “The CComPtr and CComQIPtr Smart Pointer Classes.” The specialization for BSTR fills the role of the DEFINE_AUTOMATION_TYPE_FUNCTION macro, in that it sets the _typewrapper member of _ATL_AutomationType to the CComBSTR wrapper class and sets the type member to VT_BSTR, as you can see in the following code:

1template <>
2struct _ATL_AutomationType<BSTR> {
3    typedef CComBSTR _typewrapper ;
4    enum { type = VT_BSTR};
5    static void* GetT(const BSTR& t) {
6        return t;
7    }
8};

Similarly, the specialization for IDispatch* sets type to VT_DISPATCH and _typewrapper to CComPtr; the one for IUnknown* sets type to VT_UNKNOWN and _typewrapper to CComPtr.

You should recall that, like CComVariant, CComBSTR holds in its m_str member the encapsulated BSTR as its only state. Thus, the code shown previously in the GetAt method works fine for CComSafeArrays that contain BSTR elements. Also note that the specialization shown earlier defines the GetT method differently than the other supported data types. This method is only used by the element accessor functions for multidimensional SAFEARRAY s, in which a void* pointer to the destination buffer must be provided to the SafeArrayGetElement API. The GetT implementation that the DEFINE_AUTOMATION_TYPE_FUNCTION macro generates for all the other data types returns the address of the encapsulated data. In the case of a BSTR, the data type is already a pointer to the data, so GetT is specialized to return the encapsulated type itself instead of a pointer to the encapsulated type. The specializations for IDispatch* and IUnknown* implement GetT in precisely the same way as well because they are also inherently pointer types.

CComSafeArray provides the SetAt method for writing to specific elements. The method is defined as follows:

1HRESULT SetAt(LONG lIndex, const T& t, BOOL bCopy = TRUE) {
2    bCopy;
3    ATLASSERT(m_psa != NULL);
4    LONG lLBound = GetLowerBound();
5    ATLASSERT(lIndex >= lLBound);
6    ATLASSERT(lIndex <= GetUpperBound());
7    ((T*)m_psa->pvData)[lIndex-lLBound] = t;
8    return S_OK;
9}

SetAt first ensures that the encapsulated SAFEARRAY is non-NULL and then validates the index passed in against the upper and lower bounds defined for the SAFEARRAY. The assignment statement copies the element data provided into the appropriate location in the encapsulated SAFEARRAY. Assigning a new value to a SAFEARRAY element is accomplished with code like the following:

1CComSafeArray<long> sa(5);
2long lNewVal = 14;
3// replace the 4th element with the value 14
4sa.SetAt(3, lNewVal);

The relevance of the bCopy parameter becomes evident only when you turn to the four specializations of the SetAt parameter that are provided for SAFEARRAY s: BSTR, VARIANT, IDispatch*, and IUnknown*. Each of these data types requires special handling when performing assignment, so CComSafeArray specializes SetAt to enforce the correct semantics. The specialization for BSTR first uses SysFreeString to clear the existing element in the array and then assigns it either to a copy of the provided BSTR or to the BSTR parameter itself, depending on the value of the bCopy parameter.

 1template<>
 2HRESULT CComSafeArray<BSTR>::SetAt(LONG lIndex,
 3    const BSTR& strData, BOOL bCopy) {
 4    // validation code omitted for clarity
 5
 6    BSTR strOrg = ((BSTR*)m_psa->pvData)[lIndex-lLBound];
 7    if (strOrg)
 8        ::SysFreeString(strOrg);
 9
10    if (bCopy) {
11        BSTR strTemp = ::SysAllocString(strData);
12        if (NULL == strTemp)
13            return E_OUTOFMEMORY;
14        ((BSTR*)m_psa->pvData)[lIndex-lLBound] = strTemp;
15    }
16    else
17        ((BSTR*)m_psa->pvData)[lIndex-lLBound] = strData;
18    return S_OK;
19}

When bCopy is TRUE, the caller maintains ownership of the strData BSTR parameter, because CComSafeArray will be working with its own private copy. When bCopy is FALSE, CComSafeArray takes ownership of the strData BSTR; the caller must not attempt to free it, or errors will occur when this element is accessed from the SAFEARRAY. The following code snippet demonstrates this important difference:

1BSTR bstr1 = ::SysAllocString(OLESTR("Go Longhorns!"));
2BSTR bstr2 = ::SysAllocString(OLESTR("ATL Rocks!"));
3
4CComSafeArray<BSTR> sa(5);
5sa.SetAt(2, bstr1, true);  // sa generates its own copy of bstr1
6sa.SetAt(3, bstr2, false); // sa assigns element to bstr2
7::SysFreeString(bstr1);    // ok, sa still has a copy
8::SysFreeString(bstr2);    // wrong!!! we don't own bstr2

VARIANT elements in SAFEARRAY s require special handling as well. The code for the specialized version of SetAt for VARIANTs is very similar to that shown earlier for BSTR. The main differences are that the original element is cleared using VariantClear and the copy is performed using VariantCopyInd if bCopy is TRUE. The code that implements SetAt for IDispatch* and IUnknown* type elements is identical (okay, the variable names are different``pDisp`` for IDispatch* and pUnk for IUnknown*). In either case, the original interface pointer element is Release’d before assignment and then is AddRef’d if bCopy is TRUE. Again, this means that the caller is transferring ownership of the interface pointer to the CComSafeArray if bCopy is FALSE and should not then call Release on the pointer passed to the SetAt method. CComSafeArray ultimately generates a call to Release on each element in the SAFEARRAY when the instance is destroyed, so improper handling leads to a double release of the interface pointer and the all-too-familiar exception as a reward. Both proper and improper interface pointer element assignment are demonstrated in the following code:

1IUnknown* pUnk1, pUnk2;
2// assign both pointers to refer to an object
3CComSafeArray<IUnknown*> sa(5);
4sa.SetAt(2, pUnk1, true);  // sa calls AddRef on pUnk1
5sa.SetAt(3, pUnk2, false); // sa assigns element to pUnk2
6                           // without AddRefing
7pUnk1->Release();          // ok, refcount non-zero because
8                           // of sa AddRef
9pUnk2->Release();          // wrong!!! we don't own pUnk2

The remaining two methods for accessing SAFEARRAY elements apply to multidimensional SAFEARRAY s. MultiDimGetAt and MultiDimSetAt provide read-and-write access to SAFEARRAY elements housed in a multidimensional SAFEARRAY. Both methods are very thin wrappers on top of the SafeArrayGetElement and SafeArrayPutElement API functions, respectively.

 1HRESULT MultiDimGetAt(const LONG *alIndex, T& t) {
 2    ATLASSERT(m_psa != NULL);
 3    return SafeArrayGetElement(m_psa,
 4        const_cast<LONG*>(alIndex), &t);
 5}
 6HRESULT MultiDimSetAt(const LONG *alIndex, const T& t) {
 7    ATLASSERT(m_psa != NULL);
 8    return SafeArrayPutElement(m_psa, const_cast<LONG*>(alIndex),
 9    _ATL_AutomationType<T>::GetT(t));
10}

The alIndex parameter specifies an array of SAFEARRAY indices. The first element in the alIndex array is the index of the rightmost dimension; the last element is the index of the leftmost dimension. You make use of these functions like this:

 1// 2-D array with all dimensions
 2// left-most dimension has 3 elements
 3CComSafeArrayBound bound1(3);
 4// right-most dimension has 4 elements
 5CComSafeArrayBound bound2(4);
 6
 7// equivalent C-style array indices would be [3][4]
 8CComSafeArrayBound rgBounds[] = { bound1, bound2 };
 9CComSafeArray<int> sa(rgBounds, 2);
10
11int rgIndElement1[] = { 0, 1 };    // access element at sa[1][0]
12int rgIndElement2[] = { 3, 2 };    // access element at sa[2][3]
13
14long lVal = 0;
15// retrieve value at sa[1][0]
16sa.MultiDimGetAt(rgIndElement1, lVal);
17
18// multiply value by 2 and store it
19// in element located at sa[2][3]
20sa.MultiDimSetAt(rgIndElement2, lVal*=2);

CComSafeArray Operators

CComSafeArray defines four operators that provide some syntactic convenience for accessing elements of a SAFEARRAY:

 1const typename
 2_ATL_AutomationType<T>::_typewrapper&
 3operator[](int nIndex) const {
 4    return GetAt(nIndex);
 5}
 6typename
 7_ATL_AutomationType<T>::_typewrapper&
 8operator[](int nIndex) {
 9    return GetAt(nIndex);
10}
11const typename
12_ATL_AutomationType<T>::_typewrapper&
13operator[](LONG nIndex)
14    const {
15    return GetAt(nIndex);
16}
17typename
18_ATL_AutomationType<T>::_typewrapper&
19operator[](LONG nIndex) {
20    return GetAt(nIndex);
21}

As you can see, all these operators simply delegate to the GetAt accessor method, discussed in the previous section. They differ only in the type of index and whether the const qualifier is specified. These operators enable you to write code with CComSafeArray that looks very much like the code you use to manipulate C-style array elements.

1CComSafeArray<int> sa(5);
2ATLASSERT(sa[2] == 0);
3sa[2] = 17;
4ATLASSERT(sa[2] == 17);

CComSafeArray also provides two cast operators. Both implementations are trivial, serving only to expose the encapsulated SAFEARRAY. Nevertheless, they provide some syntactic convenience in some situations, such as when you want to pass a CComSafeArray instance to a function that expects a SAFEARRAY*.

1operator const SAFEARRAY *() const {
2    return m_psa;
3}
4operator LPSAFEARRAY() {
5    return m_psa;
6}

Unfortunately, CComSafeArray does not supply one operator: an overload of operator&. Without it, you can’t use CComSafeArray as a wrapper for an out parameter like this:

1HRESULT CreateANewSafeArray( SAFEARRAY** ppsa ) {
2    *ppsa = SafeArrayCreateVector(VT_I4, 1, 15 );
3    return S_OK;
4}
5HRESULT UseCreatedSafeArray( ) {
6    CComSafeArray< int > sa;
7    HRESULT hr = CreateANewSafeArray( &sa );
8}

The previous code will not compile but will fail with this error:

 1error C2664: CreateANewSafeArray : cannot convert
 2parameter 1 from
 3    ATL::CComSafeArray<T> *__w64 to SAFEARRAY **
 4        with
 5        [
 6            T=int
 7        ]
 8        Types pointed to are unrelated;
 9        conversion requires reinterpret_cast,
10        C-style cast or function-style cast

This use differs from most of the other ATL smart types, which let you do exactly this, but there’s a good reason for the disparity. Imagine that the ATL team had included the overloaded operator&. What happens in this case?

1HRESULT CreateANewSafeArray( SAFEARRAY** ppsa ) {
2    *ppsa = SafeArrayCreateVector(VT_BSTR, 1, 15 );
3    return S_OK;
4}
5
6HRESULT UseCreatedSafeArray( ) {
7    CComSafeArray< int > sa;
8    HRESULT hr = CreateANewSafeArray( &sa );
9}

The C++ compiler can’t tell what the returned SAFEARRAY actually contains; that information is available only at runtime. The compiler would have to allow the conversion, and we now have a CComSafeArray<int> wrapping a SAFEARRAY that actually contains BSTR s. Nothing good can come of this, so the operator& overload was left out. Instead, you can do this:

1HRESULT UseCreatedSafeArray( ) {
2    SAFEARRAY *psa = null;
3    HRESULT hr = CreateANewSafeArray( &psa );
4    CComSafeArray< int > sa;
5    sa.Attach( psa );
6}

The error will now be detected at runtime inside the CComSafeArray::Attach method.

The GetSafeArrayPtr() method, mentioned earlier, explicitly retrieves a pointer to the stored SAFEARRAY. It can be used like this:

1HRESULT UseCreatedSafeArray( ) {
2    CComSafeArray< int > sa;
3    HRESULT hr = CreateANewSafeArray(sa.GetSafeArrayPtr());
4}

However, this use bypasses the runtime type check in the Attach method and is not recommended for this reason.

The CComPtr and CComQIPtr Smart Pointer Classes

A Review of Smart Pointers

A smart pointer is an object that behaves like a pointer. That is, you can use an instance of a smart pointer class in many of the places you normally use a pointer. However, using a smart pointer provides some advantages over using a raw pointer. For example, a smart interface pointer class can do the following:

  • Release the encapsulated interface pointer when the class destructor executes.

  • Automatically release its interface pointer during exception handling when you allocate the smart interface pointer on the stack. This reduces the need to write explicit exception-handling code.

  • Release the encapsulated interface pointer before overwriting it during an assignment operation.

  • Call AddRef on the interface pointer received during an assignment operation.

  • Provide different constructors to initialize a new smart pointer through convenient mechanisms.

  • Be used in many, but not all, the places where you would conventionally use a raw interface pointer.

ATL provides two smart pointer classes: CComPtr and CComQIPtr. The CComPtr class is a smart COM interface pointer class. You create instances tailored for a specific type of interface pointer. For example, the first line of the following code creates a smart IUnknown interface pointer. The second line creates a smart INamedObject custom interface pointer:

1CComPtr<IUnknown>     punk;
2CComPtr<INamedObject> pno;

The CComQIPtr class is a smarter COM interface pointer class that does everything CComPtr does and more. When you assign to a CComQIPtr instance an interface pointer of a different type than the smart pointer, the class calls QueryInterface on the provided interface pointer:

1CComPtr<IUnknown>       punk = /* Init to some IUnknown* */ ;
2CComQIPtr<INamedObject> pno = punk; // Calls punk->QI
3                                    // (IID_INamedObject, ...)

The CComPtr and CComQIPtr Classes

The CComPtr and CComQIPtr classes are similar, with the exception of initialization and assignment. In fact, they’re so similar that CComQIPtr actually derives from CComPtr and CComPtr, in turn, derives from another class, CComPtrBase. This latter class defines the actual storage of the underlying raw pointer, and the actual reference-counting operations on that raw pointer. CComPtr and CComQIPtr add constructors and assignment operators. Because of the inheritance relationship between these classes, all the following comments about the CComPtr class apply equally to the CComQIPtr class unless specifically stated otherwise.

The atlcomcli.h file contains the definition of all three classes. The only state each class maintains is a single public member variable, T* p. This state is defined in the CComPtrBase base class:

 1template <class T>
 2class CComPtrBase {
 3    ...
 4    T* p;
 5};
 6
 7template <class T>
 8class CComPtr : public CComPtrBase<T>
 9{ ... };
10
11template <class T, const IID* piid = &__uuidof(T)>
12class CComQIPtr : public CComPtr<T>
13{ ... };

The first (or, in the case of CComPtr, only) template parameter specifies the type of the smart interface pointer. The second template parameter to the CComQIPtr class specifies the interface ID for the smart pointer. By default, it is the globally unique identifier (GUID) associated with the class of the first parameter. Here are a few examples that use these smart pointer classes. The middle three examples are all equivalent:

1CComPtr<IUnknown> punk;    // Smart IUnknown*
2CComPtr<INamedObject> pno; // Smart INamedObject*
3
4CComQIPtr<INamedObject> pno;
5CComQIPtr<INamedObject, &__uuidof(INamedObject)> pno;
6CComQIPtr<INamedObject, &IID_INamedObject> pno;
7
8CComQIPtr<IDispatch, &IID_ISomeDual> pdisp;

Constructors and Destructor

A CComPtr object can be initialized with an interface pointer of the appropriate type. That is, a CComPtr<IFoo> object can be initialized using an IFoo* or another CComPtr<IFoo> object. Using any other type produces a compiler error. The actual implementation of this behavior is in the CComPtrBase class. The default constructor initializes the internal interface pointer to NULL. The other constructors initialize the internal interface pointer to the specified interface pointer. When the specified value is non- NULL, the constructor calls the AddRef method. The destructor calls the Release method on a non-NULL interface pointer.

CComPtr has a special copy constructor that pulls out the underlying raw interface pointer and passes it to the CComPtrBase base class, thus guaranteeing proper AddRef and Release calls.

1CComPtrBase()     { p = NULL; }
2CComPtrBase(T* p) { if ((p = lp) != NULL) p->AddRef(); }
3~CComPtrBase()    { if (p) p->Release(); }
4
5CComPtr(const CComPtr<T>& lp) : CComPtrBase<T>(lp.p) { }

A CComQIPtr object can be initialized with an interface pointer of any type. When the initialization value is the same type as the smart pointer, the constructor simply AddRef’s the provided pointer via the base class’s constructor:

1CComQIPtr(T* lp)  :
2    CComPtr<T>(lp)
3    {}
4
5CComQIPtr(const CComQIPtr<T,piid>& lp) :
6    CComPtr<T>(lp.p)
7    {}

However, specifying a different type invokes the following constructor, which queries the provided interface pointer for the appropriate interface:

1CComQIPtr(IUnknown* lp)
2    { if (lp != NULL) lp->QueryInterface(*piid, (void **)&p); }

A constructor can never fail. Nevertheless, the QueryInterface call might not succeed. The CComQIPtr class sets the internal pointer to NULL when it cannot obtain the required interface. Therefore, you use code such as the following to test whether the object initializes:

1void func (IUnknown* punk) {
2    CComQIPtr<INamedObject> pno (punk);
3    if (pno) {
4        // Can call SomeMethod because the QI worked
5        pno->SomeMethod ();
6    }
7}

You can tell whether the query failed by checking for a NULL pointer, but you cannot determine why it fails. The constructor doesn’t save the HRESULT from a failed QueryInterface call.

Initialization

The CComPtr class defines three assignment operators; the CComQIPtr class defines three slightly different ones. All the assignment operators do the same actions:

  • Release the current interface pointer when it’s non- NULL.

  • AddRef the source interface pointer when it’s non- NULL.

  • Save the source interface pointer as the current interface pointer.

The CComPtr assignment operators are shown here:

1// CComPtr assignment operators
2T* operator=(T* lp);
3template <typename Q> T* operator=(const CComPtr<Q>& lp);
4T* operator=(const CComPtr<T>& lp);

The templated version of operator= is interesting. It enables you to assign arbitrary CComPtrs to each other with proper QueryInterface calls made, if necessary. For example, this is now legal:

1CComPtr< IFoo > fooPtr = this;
2CComPtr< IBar > barPtr;
3
4barPtr = fooPtr;

This begs the question: Why have CComQIPtr at all if CComPtr does the work, too? It appears that the ATL team is moving toward having a single smart interface pointer instead of two and is leaving CComQIPtr in place for backward compatibility.

The CComQIPtr assignment operators are mostly the same. The only one that does any interesting work is the overload that takes an IUnknown*. This queries a non- NULL source interface pointer for the appropriate interface to save. You receive a NULL pointer when the QueryInterface calls fail. As with the equivalent constructor, the HRESULT for a failed query is not available.

1// CComQIPtr assignment operators
2T* operator=(T* lp);
3T* operator=(const CComQIPtr<T>& lp);
4T* operator=(IUnknown* lp);

Typically, you use the CComQIPtr assignment operator to perform a QueryInterface call. You immediately follow the assignment with a NULL pointer test, as follows:

 1// Member variable holding object
 2CComQIPtr<IExpectedInterface> m_object;
 3
 4STDMETHODIMP put_Object (IUnknown* punk) {
 5                      // Releases current object, if any, and
 6    m_object = punk;  // queries for the expected interface
 7    if (!m_object)
 8        return E_UNEXPECTED;
 9    return S_OK;
10}

Object Instantiation Methods

The CComPtrBase class provides an overloaded method, called CoCreateInstance, that you can use to instantiate an object and retrieve an interface pointer on the object. The method has two forms. The first requires the class identifier (CLSID) of the class to instantiate. The second requires the programmatic identifier (ProgID) of the class to instantiate. Both overloaded methods accept optional parameters for the controlling unknown and class context for the instantiation. The controlling unknown parameter defaults to NULL, the normal case, which indicates no aggregation. The class context parameter defaults to CLSCTX_ALL, indicating that any available server can service the request.

 1HRESULT CoCreateInstance (REFCLSID rclsid,
 2                          LPUNKNOWN pUnkOuter = NULL,
 3                          DWORD dwClsContext = CLSCTX_ALL) {
 4    ATLASSERT(p == NULL);
 5    return ::CoCreateInstance(rclsid, pUnkOuter,
 6        dwClsContext, __uuidof(T), (void**)&p);
 7}
 8
 9HRESULT CoCreateInstance (LPCOLESTR szProgID,
10                          LPUNKNOWN pUnkOuter = NULL,
11                          DWORD dwClsContext = CLSCTX_ALL);

Notice how the preceding code for the first CoCreateInstance method creates an instance of the specified class. It passes the parameters of the method to the CoCreateInstance COM API and, additionally, requests that the initial interface be the interface that the smart pointer class supports. (This is the purpose of the __uuidof(T) expression.) The second overloaded CoCreateInstance method translates the provided ProgID to a CLSID and then creates the instance in the same manner as the first method.

Therefore, the following code is equivalent (although the smart pointer code is easier to read, in my opinion). The first instantiation request explicitly uses the CoCreateInstance COM API. The second uses the smart pointer CoCreateInstance method.

 1ISpeaker* pSpeaker;
 2HRESULT hr =
 3 ::CoCreateInstance (__uuidof (Demagogue), NULL, CLSCTX_ALL,
 4                     __uuidof (ISpeaker_, (void**) &pSpeaker);
 5//... Use the interface
 6pSpeaker->Release () ;
 7
 8
 9CComPtr<ISpeaker> pSpeaker;
10HRESULT hr = pSpeaker.CoCreateInstance (__uuidof (Demogogue));
11//... Use the interface. It releases when pSpeaker leaves scope

CComPtr and CComQIPtr Operations

Because a smart interface pointer should behave as much as possible like a raw interface pointer, the CComPtrBase class defines some operators to make the smart pointer objects act like pointers. For example, when you dereference a pointer using operator*(), you expect to receive a reference to whatever the pointer points. So dereferencing a smart interface pointer should produce a reference to whatever the underlying interface pointer points to. And it does:

1T& operator*() const { ATLENSURE(p!=NULL); return *p; }

Note that the operator*() method kindly asserts (via the ATLENSURE macro [2] ) when you attempt to dereference a NULL smart interface pointer in a debug build of your component. Of course, I’ve always considered the General Protection Fault message box to be an equivalent assertion. However, the ATLENSURE macro produces a more programmer-friendly indication of the error location.

To maintain the semblance of a pointer, taking the address of a smart pointer object; that is, invoking operator&() should actually return the address of the underlying raw pointer. Note that the issue here isn’t the actual binary value returned. A smart pointer contains only the underlying raw interface pointer as its state. Therefore, a smart pointer occupies exactly the same amount of storage as a raw interface pointer. The address of a smart pointer object and the address of its internal member variable are the same binary value.

Without overriding CComPtrBase<T>::operator&(), taking the address of an instance returns a CComPtrBase<T>*. To have a smart pointer class maintain the same pointer semantics as a pointer of type T*, the operator&() method for the class must return a T**.

1T** operator&() { ATLASSERT(p==NULL); return &p; }

Note that this operator asserts when you take the address of a non-NULL smart interface pointer because you might dereference the returned address and overwrite the internal member variable without properly releasing the interface pointer. It asserts to protect the semantics of the pointer and keep you from accidentally stomping on the pointer. This behavior, however, keeps you from using a smart interface pointer as an [in,out] function parameter.

1STDMETHODIMP SomeClass::UpdateObject (
2    /* [in, out] */ IExpected** ppExpected);
3
4CComPtr<IExpected> pE = /* Initialize to some value */ ;
5
6pobj->UpdateObject (&pE); // Asserts in debug build because
7                          // pE is non-NULL

When you really want to use a smart pointer in this way, take the address of the member variable:

1pobj->UpdateObject (&pE.p);

CComPtr and CComQIPtr Resource-Management Operations

A smart interface pointer represents a resource, albeit one that tries to manage itself properly. Sometimes, though, you want to manage the resource explicitly. For example, you must release all interface pointers before calling the CoUninitialize method. This means that you can’t wait for the destructor of a CComPtr object to release the interface pointer when you allocate the object as a global or static variableor even a local variable in main(). The destructor for global and static variables executes only after the main function exits, long after CoUninitialize runs.

You can release the internal interface pointer by assigning NULL to the smart pointer. Alternatively and more explicitly, you can call the Release method.

 1int main( ) {
 2    HRESULT hr = CoInitialize( NULL );
 3    If (FAILED(hr)) return 1;  // Something is seriously wrong
 4
 5    CComPtr<IUnknown> punk = /* Initialize to some object */ ;
 6    ...
 7    punk.Release( ); // Must Release before CoUninitialize!
 8
 9    CoUninitialize( );
10}

Note that the previous code calls the smart pointer object’s CComPtr<T>::Release method because it uses the dot operator to reference the object. It does not directly call the underlying interface pointer’s IUnknown::Release method, as you might expect. The smart pointer’s CComPtrBase<T>::Release method calls the underlying interface pointer’s IUnknown::Release method and sets the internal interface pointer to NULL. This prevents the destructor from releasing the interface again. Here is the smart pointer’s Release method:

1void Release() {
2     T* pTemp = p;
3     if (pTemp) {
4         p = NULL;
5         pTemp->Release();
6     }
7}

It’s not immediately obvious why the CComPtrBase<T>::Release method doesn’t simply call IUnknown::Release using its p member variable. Instead, it copies the interface pointer member variable into the local variable, sets the member variable to NULL, and then releases the interface using the temporary variable. This approach avoids a situation in which the interface the smart pointer holds is released twice.

For example, assume that the smart pointer is a member variable of class A and that the smart pointer holds a reference to object B. You call the smart pointer’s .Release method. The smart pointer releases its reference to object B. Object B, in turn, holds a reference to the class A instance containing the smart pointer. Object B decides to release its reference to the class A instance. The class A instance decides to destruct, which invokes the destructor for the smart pointer member variable. The destructor detects that the interface pointer is non-NULL, so it releases the interface again. [3]

In releases of ATL earlier than version 3, the following code would compile successfully and would release the interface pointer twice. Note the use of the arrow operator.

1punk->Release( );         // Wrong! Wrong! Wrong!

In those releases of ATL, the arrow operator returned the underlying interface pointer. Therefore, the previous line actually called the IUnknown::Release function, not the CComPtr<T>::Release method, as expected. This left the smart pointer’s interface pointer member variable non-NULL, so the destructor would eventually release the interface a second time.

This was a nasty bug to find. A smart pointer class encourages you to think about an instance as if it were an interface pointer. However, in this particular case, you shouldn’t use the arrow operator (which you would if it actually was a pointer); you had to use the dot operator because it was actually an object. What’s worse, the compiler didn’t tell you when you got it wrong.

This changed in version 3 of ATL. Note that the current definition of the arrow operator returns a _NoAddRefReleaseOnCComPtr<T>* value:

1_NoAddRefReleaseOnCComPtr<T>* operator->() const {
2    ATLASSERT(p!=NULL); return (_NoAddRefReleaseOnCComPtr<T>*)p;
3}

This is a simple template class whose only purpose is to make the AddRef and Release methods inaccessible:

1template <class T>
2class _NoAddRefReleaseOnCComPtr : public T {
3    private:
4        STDMETHOD_(ULONG, AddRef)()=0;
5        STDMETHOD_(ULONG, Release)()=0;
6};

The _NoAddRefReleaseOnCComPtr<T> template class derives from the interface being returned. Therefore, it inherits all the methods of the interface. The class then overrides the AddRef and Release methods, making them private and purely virtual. Now you get the following compiler error when you use the arrow operator to call either of these methods:

1error C2248: 'Release' : cannot access private member declared
2  in class 'ATL::_NoAddRefReleaseOnCComPtr<T>'

The CopyTo Method

The CopyTo method makes an AddRef’ed copy of the interface pointer and places it in the specified location. Therefore, the CopyTo method produces an interface pointer that has a lifetime that is separate from the lifetime of the smart pointer that it copies.

1HRESULT CopyTo(T** ppT) {
2    ATLASSERT(ppT != NULL);
3    if (ppT == NULL) return E_POINTER;
4    *ppT = p;
5    if (p) p->AddRef();
6    return S_OK;
7}

Often, you use the CopyTo method to copy a smart pointer to an [out] parameter. An [out] interface pointer must be AddRef’ed by the code returning the pointer:

1STDMETHODIMP SomeClass::get_Object(
2/* [out] */ IExpected** ppExpected) {
3    // Interface saved in member m_object
4    // of type CComPtr<IExpected>
5
6    // Correctly AddRefs pointer
7    return m_object.CopyTo (ppExpected) ;
8}

Watch out for the following codeit probably doesn’t do what you expect, and it isn’t correct:

1STDMETHODIMP SomeClass::get_Object (
2/* [out] */ IExpected** ppExpected) {
3    // Interface saved in member m_object
4    // of type CComPtr<IExpected>
5    *ppExpected = m_object ;  // Wrong! Does not AddRef pointer!
6}

The Type-Cast Operator

When you assign a smart pointer to a raw pointer, you implicitly invoke the operator T() method. In other words, you cast the smart pointer to its underlying type. Notice that operator T() doesn’t AddRef the pointer it returns:

1operator T*() const { return (T*) p; }

That’s because you don’t want the AddRef in the following case:

1STDMETHODIMP SomeClass::put_Object (
2    /* [in] */ IExpected* pExpected);
3
4// Interface saved in member m_object of type CComPtr<IExpected>
5// Correctly does not AddRef pointer!
6pObj->put_Object (m_object) ;

The Detach and Attach Methods

When you want to transfer ownership of the interface pointer in a CComPtr instance from the instance to an equivalent raw pointer, use the Detach method. It returns the underlying interface pointer and sets the smart pointer to NULL, ensuring that the destructor doesn’t release the interface. The client calling Detach becomes responsible for releasing the interface.

1T* Detach() { T* pt = p; p = NULL; return pt; }

You often use Detach when you need to return to a caller an interface pointer that you no longer need. Instead of providing the caller an AddRef’ed copy of the interface and then immediately releasing your held interface pointer, you can simply transfer the reference to the caller, thus avoiding extraneous AddRef / Release calls. Yes, it’s a minor optimization, but it’s also simple:

1STDMETHODIMP SomeClass::get_Object (
2/* [out] */ IExpected** ppExpected) {
3  CComPtr<IExpected> pobj = /* Initialize the smart pointer */ ;
4  *ppExpected = pobj->Detach(); // Destructor no longer Releases
5  return S_OK;
6}

When you want to transfer ownership of a raw interface pointer to a smart pointer, use the Attach method. It releases the interface pointer that the smart pointer holds and then sets the smart pointer to use the raw pointer. Note that, again, this technique avoids extraneous AddRef / Release calls and is a useful minor optimization:

1void Attach(T* p2) { if (p) p->Release(); p = p2; }

Client code can use the Attach method to assume ownership of a raw interface pointer that it receives as an [out] parameter. The function that provides an [out] parameter is transferring ownership of the interface pointer to the caller.

 1STDMETHODIMP SomeClass::get_Object (
 2  /* [out] */ IExpected** ppObject);
 3
 4void VerboseGetOption () {
 5  IExpected* p;
 6  pObj->get_Object (&p) ;
 7
 8  CComPtr<IExpected> pE;
 9  pE.Attach (p); // Destructor now releases the interface pointer
10  // Let the exceptions fall where they may now!!!
11  CallSomeFunctionWhichThrowsExceptions();
12}

Miscellaneous Smart Pointer Methods

The smart pointer classes also provide useful shorthand syntax for querying for a new interface: the QueryInterface method. It takes one parameter: the address of a variable that is of the type of the desired interface.

1template <class Q>
2HRESULT QueryInterface(Q** pp) const {
3    ATLASSERT(pp != NULL && *pp == NULL);
4    return p->QueryInterface(__uuidof(Q), (void**)pp);
5}

This method reduces the chance of making the common mistake of querying for one interface (for example, IID_IBar), but specifying a different type of pointer for the returned value (for example, IFoo*).

1CComPtr<IFoo> pfoo = /* Initialize to some IFoo */
2IBar* pbar;
3
4// We specify an IBar variable so the method queries for IID_IBar
5HRESULT hr = pfoo.QueryInterface(&pBar);

Use the IsEqualObject method to determine whether two interface pointers refer to the same object:

1bool IsEqualObject(IUnknown* pOther);

This method performs the test for COM identity: Query each interface for IID_IUnknown and compare the results. A COM object must always return the same pointer value when asked for its IUnknown interface. The IsEqualObject method expands a little on the COM identity test. It considers two NULL interface pointers to be equal objects.

1bool SameObjects(IUnknown* punk1, IUnknown* punk2) {
2    CComPtr<IUnknown> p (punk1);
3    return p.IsEqualObject (punk2);
4}
5
6IUnknown* punk1 = NULL;
7IUnknown* punk2 = NULL;
8ATLASSERT (SameObjects(punk1, punk2); // true

The SetSite method associates a site object (specified by the punkParent parameter) with the object referenced by the internal pointer. The smart pointer must point to an object that implements the IObjectWithSite interface.

1HRESULT SetSite(IUnknown* punkParent);

The Advise method associates a connection point sink object with the object the smart interface pointer references (which is the event source object). The first parameter is the sink interface. You specify the sink interface ID as the second parameter. The third parameter is an output parameter. The Advise method returns a token through this parameter that uniquely identifies this connection.

1HRESULT Advise(IUnknown* pUnk, const IID& iid, LPDWORD pdw);
1CComPtr<ISource> ps /* Initialized via some mechanism */ ;
2ISomeSink* psink = /* Initialized via some mechanism */ ;
3DWORD dwCookie;
4
5ps->Advise (psink, __uuidof(ISomeSink), &dwCookie);

There is no Unadvise smart pointer method to end the connection because the pointer is not needed for the Unadvise. To break the connection, you need only the cookie, the sink interface identifier (IID), and an event source reference.

CComPtr Comparison Operators

Three operators provide comparison operations on a smart pointer. The operator!() method returns true when the interface pointer is NULL. The operator==() method returns true when the comparison operand is equal to the interface pointer. The operator<() method is rather useless because it compares two interface pointers using their binary values. However, a class needs these comparison operators so that STL collections of class instances work properly.

1bool operator!() const       { return (p == NULL); }
2bool operator< (T* pT) const { return p <  pT; }
3bool operator==(T* pT) const { return p == pT; }
4bool operator!=(T* pT) const { return !operator==(pT); }

Using these comparison operators, all the following styles of code work:

1CComPtr<IFoo> pFoo;
2// Tests for pFoo.p == NULL using operator!
3if (!pFoo)        {...}
4// Tests for pFoo.p == NULL using operator==
5if (pFoo == NULL) {...}
6// Converts pFoo to T*, then compares to NULL
7if (NULL == pFoo) {...}

The CComPtr Specialization for IDispatch

It’s a royal pain to call an object’s methods and properties using the IDispatch::Invoke method. You have to package all the arguments into VARIANT structures, build an array of those VARIANT s, and translate the name of the method to a DISPID. It’s not only extremely difficult, but it’s all tedious and error-prone coding. Here’s an example of what it takes to make a simple call to the following ICalc::Add method:

 1// component IDL file
 2[
 3    object,
 4    uuid(2F6C88D7-C2BF-4933-81FA-3FBAFC3FC34B),
 5    dual,
 6]
 7interface ICalc : IDispatch {
 8    [id(1)] HRESULT Add([in] DOUBLE Op1,
 9        [in] DOUBLE Op2, [out,retval] DOUBLE* Result);
10};
11
12// client.cpp
13HRESULT CallAdd(IDispatch* pdisp) {
14  // Get the DISPID
15  LPOLESTR pszMethod = OLESTR("Add");
16  DISPID dispid;
17  hr = pdisp->GetIDsOfNames(IID_NULL,
18                            &pszMethod,
19                            1,
20                            LOCALE_SYSTEM_DEFAULT,
21                            &dispid);
22
23  if (FAILED(hr))
24       return hr;
25
26  // Set up the parameters
27  DISPPARAMS dispparms;
28  memset(&dispparms, 0, sizeof(DISPPARAMS));
29  dispparms.cArgs = 2;
30
31  // Parameters are passed right to left
32  VARIANTARG rgvarg[2];
33  rgvarg[0].vt = VT_R8;
34  rgvarg[0].dblVal = 6;
35  rgvarg[1].vt = VT_R8;
36  rgvarg[1].dblVal = 7;
37
38  dispparms.rgvarg = &rgvarg[0];
39
40  // Set up variable to hold method return value
41  VARIANTARG vaResult;
42  ::VariantInit(&vaResult);
43
44  // Invoke the method
45  hr = pdisp->Invoke(dispid,
46                     IID_NULL,
47                     LOCALE_SYSTEM_DEFAULT,
48                     DISPATCH_METHOD,
49                     &dispparms,
50                     &vaResult,
51                     NULL,
52                     NULL);
53
54  // vaResult now holds sum of 6 and 7
55}

Ouch! That’s pretty painful for such a simple method call. The code to call a property on an IDispatch interface is very similar. Fortunately, ATL provides relief from writing code like this.

CComPtr provides a specialization for dealing with the IDispatch interface:

 1//specialization for IDispatch
 2template <>
 3class CComPtr<IDispatch> : public CComPtrBase<IDispatch> {
 4public:
 5    CComPtr() {}
 6    CComPtr(IDispatch* lp)  :
 7        CComPtrBase<IDispatch>(lp) {}
 8    CComPtr(const CComPtr<IDispatch>& lp) :
 9        CComPtrBase<IDispatch>(lp.p) {}
10};

Because this class derives from CComPtrBase, it inherits the typical smart pointer methods. I examine only the ones that differ significantly from those discussed for the CComPtr and CComQIPtr classes.

Property Accessor and Mutator Methods

A few of the methods make it much easier to get and set properties on an object using the object’s IDispatch interface. First, you can get the DISPID for a property, given its string name, by calling the GetIDOfName method:

1HRESULT GetIDOfName(LPCOLESTR lpsz, DISPID* pdispid);

When you have the DISPID for a property, you can get and set the property’s value using the GetProperty and PutProperty methods. You specify the DISPID of the property to get or set and send or receive the new value in a VARIANT structure:

1HRESULT GetProperty(DISPID dwDispID, VARIANT* pVar);
2HRESULT PutProperty(DISPID dwDispID, VARIANT* pVar);

You can skip the initial step and get and set a property given only its name using the well-named GetPropertyByName and PutPropertyByName methods:

1HRESULT GetPropertyByName(LPCOLESTR lpsz, VARIANT* pVar);
2HRESULT PutPropertyByName(LPCOLESTR lpsz, VARIANT* pVar);

Method Invocation Helper Functions

The CComPtr<IDispatch> specialization has a number of methods that are customized for the frequent cases of calling an object’s method(s) using IDispatch. Four basic variations exist:

  • Call a method by DISPID or name, passing zero parameters.

  • Call a method by DISPID or name, passing one parameter.

  • Call a method by DISPID or name, passing two parameters.

  • Call a method by DISPID or name, passing an array of N parameters.

Each variation expects the DISPID or name of the method to invoke, the arguments, and an optional return value.

 1HRESULT Invoke0(DISPID dispid, VARIANT* pvarRet = NULL);
 2HRESULT Invoke0(LPCOLESTR lpszName, VARIANT* pvarRet = NULL);
 3HRESULT Invoke1(DISPID dispid, VARIANT* pvarParam1,
 4    VARIANT* pvarRet = NULL);
 5HRESULT Invoke1(LPCOLESTR lpszName,
 6        VARIANT* pvarParam1, VARIANT* pvarRet = NULL);
 7HRESULT Invoke2(DISPID dispid,
 8        VARIANT* pvarParam1, VARIANT* pvarParam2,
 9        VARIANT* pvarRet = NULL);
10HRESULT Invoke2(LPCOLESTR lpszName,
11        VARIANT* pvarParam1, VARIANT* pvarParam2,
12        VARIANT* pvarRet = NULL);
13HRESULT InvokeN(DISPID dispid,
14        VARIANT* pvarParams, int nParams,
15        VARIANT* pvarRet = NULL);
16HRESULT InvokeN(LPCOLESTR lpszName,
17        VARIANT* pvarParams, int nParams,
18        VARIANT* pvarRet = NULL);

Note that when you are creating the parameter arrays, the parameters must be in reverse order: The last parameter should be at element 0, the next-to-last at element 1, and so on.

Using these helper functions, calling the Add method gets much simpler:

 1HRESULT TheEasyWay( IDispatch *spCalcDisp ) {
 2  CComPtr< IDispatch > spCalcDisp( pCalcDisp );
 3
 4  CComVariant varOp1( 6.0 );
 5  CComVariant varOp2( 7.0 );
 6  CComVariant varResult;
 7  HRESULT hr = spCalcDisp.Invoke2( OLESTR( "Add" ),
 8    &varOp1, &varOp2, &varResult );
 9  // varResult now holds sum of 6 and 7
10}

Finally, two static member functions exist: GetProperty and SetProperty. You can use these methods to get and set a property using its DISPID, even if you haven’t encapsulated the IDispatch pointer in a CComPtr<IDispatch>.

1static HRESULT GetProperty(IDispatch* pDisp, DISPID dwDispID,
2    VARIANT* pVar);
3static HRESULT PutProperty(IDispatch* pDisp, DISPID dwDispID,
4    VARIANT* pVar);

Here’s an example:

 1HRESULT GetCount(IDispatch* pdisp, long* pCount) {
 2    *pCount = 0;
 3    const int DISPID_COUNT = 1;
 4
 5    CComVariant v;
 6    CComPtr<IDispatch>::GetProperty (pdisp, DISPID_COUNT, &v);
 7
 8    HRESULT hr = v.ChangeType (VT_I4);
 9    If (SUCCEEDED (hr))
10        *pCount = V_I4(&v) ;
11    return hr;
12}

The CComGITPtr Class

The Global Interface Table (GIT) provides a per-process cache for storing COM interfaces that you can efficiently unmarshal and access from any apartment in a process. COM objects that aggregate the free-threaded marshaler typically use the GIT to unmarshal interfaces that they hold as state because the object never knows which apartment it might be called from. The GIT provides a convenient place where objects that export an interface from their apartment can register interfaces, and where objects that import interfaces into their apartment can unmarshal and use the interface.

Typically, several steps are involved in using the GIT. First, the exporting apartment must use CoCreateInstance to create an instance of the GIT and obtain an IGlobalInterfaceTable pointer. The exporting apartment then calls IGlobalInterfaceTable::RegisterInterfaceInGlobal to register the interface in the GIT. As a result of the call to RegisterInterfaceInGlobal, the exporting apartment receives an apartment-neutral cookie that can safely be passed to other apartments (but not other processes) for unmarshaling. Any number of objects in any importing apartment can then use this cookie to retrieve an interface reference that is properly unmarshaled for use in their own apartment.

The code in the exporting apartment might typically look like the following:

 1HRESULT RegisterMyInterface(IMyInterface* pmi, DWORD* pdwCookie) {
 2  // this is usually a global
 3  IGlobalInterfaceTable* g_pGIT = NULL;
 4  HRESULT hr = ::CoCreateInstance(CLSID_StdGlobalInterfaceTable,
 5                                  NULL,
 6                                  CLSCTX_INPROC_SERVER,
 7                                  IID_IGlobalInterfaceTable,
 8                                  (void**)&g_pGIT);
 9ATLASSERT(SUCCEEDED(hr));
10hr = g_pGIT->RegisterInterfaceInGlobal(pmi,
11  __uuidof(pmi), pdwCookie);
12return hr;
13}

The pdwCookie returned to the exporting apartment then is passed to another apartment. By using that cookie, any code in that apartment can retrieve the interface pointer registered in the GIT.

1HRESULT ReadMyInterface(DWORD dwCookie) {
2    // ... GIT pointer obtained elsewhere
3    IMyInterface* pmi = NULL;
4    hr = g_pGIT->GetInterfaceFromGlobal(dwCookie,
5    __uuidof(pmi), (void**)&pmi);
6    // use pmi as usual
7    return hr;
8}

The exporting apartment removes the interface from the GIT by calling IGlobalInterfaceTable::RevokeInterfaceFromGlobal and passing in the cookie it originally received.

ATL simplifies the coding required to perform the following steps by encapsulating the GIT functions in the CComGITPtr smart pointer class. This class is defined in atlbase.h as follows:

1template <class T>
2class CComGITPtr
3{
4    // ...
5    DWORD m_dwCookie;
6};

This class accepts an interface type as its template parameter and holds as its only state the cookie for the interface that will be registered in the GIT. The previously described operations that the exporting apartment performed are encapsulated by CComGITPtr. Under the covers, CComGITPtr simply manipulates the same GIT functions that would otherwise be invoked manually. Even the creation and caching of the GIT itself is managed for you. CComGITPtr retrieves a reference to the GIT from CAtlModule, which instantiates the GIT automatically the first time it is accessed and caches the resulting interface pointer for subsequent accesses. This class holds all sorts of information that is global to a COM server; this is discussed in detail in Chapter 5, “COM Servers.”

CComGITPtr instances can be instantiated with four different constructors:

1CComGITPtr() ;
2CComGITPtr(T* p);
3CComGITPtr(const CComGITPtr& git);
4explicit CComGITPtr(DWORD dwCookie) ;

The first constructor simply initializes the m_dwCookie member variable to zero. The second constructor accepts an interface pointer. This constructor retrieves an IGlobalInterfaceTable pointer to a global instance of the GIT and calls RegisterInterfaceInGlobal. The resulting cookie is cached in m_dwCookie. The third constructor accepts a reference to an existing instance of CComGITPtr. This overload retrieves the interface associated with the passed-in git parameter, reregisters it in the GIT to get a second cookie, and stores the new cookie in its own m_dwCookie member variable. This leaves the two CComGITPtr s with separate registered copies of the same interface pointer. The fourth constructor accepts a cookie directly and caches the value. One nice thing about this constructor is that, in debug builds, the implementation tries to validate the cookie by retrieving an interface from the GIT using the cookie. The constructor asserts if this fails.

The three assignment operators CComGITPtr supplies perform operations identical to those of the corresponding constructors:

1CComGITPtr<T>& operator=(T* p)
2CComGITPtr<T>& operator=(const CComGITPtr<T>& git)
3CComGITPtr<T>& operator=(DWORD dwCookie)

What’s particularly nice about CComGITPtr is that the destructor takes care of the required GIT cleanup when the instance goes out of scope. Beware, thoughthis can get you into a bit of trouble if you’re not careful, as you’ll learn at the end of this section.

1~CComGITPtr() { Revoke(); }

As you can see, the destructor simply delegates its work to the Revoke method, which takes care of retrieving an IGlobalInterfaceTable pointer and using it to call RevokeInterfaceFromGlobal.

 1HRESULT Revoke() {
 2    HRESULT hr = S_OK;
 3    if (m_dwCookie != 0) {
 4        CComPtr<IGlobalInterfaceTable> spGIT;
 5        HRESULT hr = E_FAIL;
 6        hr = AtlGetGITPtr(&spGIT);
 7
 8        ATLASSERT(spGIT != NULL);
 9        ATLASSERT(SUCCEEDED(hr));
10        if (FAILED(hr))
11            return hr;
12
13        hr = spGIT->RevokeInterfaceFromGlobal(m_dwCookie);
14        if (SUCCEEDED(hr))
15        m_dwCookie = 0;
16    }
17    return hr;
18}

If you are working with a CComGITPtr instance that has already been initialized, you can use one of the Attach methods to associate a different interface with the instance:

1HRESULT Attach(T* p) ;
2HRESULT Attach(DWORD dwCookie) ;

The first version of Attach calls RevokeInterfaceFromGlobal if m_dwCookie is nonzero – that is, if this CComGITPtr is already managing an interface registered in the GIT. It then calls RegisterInterfaceInGlobal using the new interface p passed in and stores the resulting cookie. The second overload also removes the interface it managed from the GIT (if necessary) and then simply caches the cookie provided.

Correspondingly, the Detach method can be used to disassociate the interface from the CComGITPtr instance.

1DWORD Detach() ;

This method simply returns the stored cookie value and sets m_dwCookie to zero. This means that the caller has now taken ownership of the registered interface pointer and must eventually call RevokeInterfaceFromGlobal.

These methods greatly simplify the code needed to register an interface pointer in the GIT and manage that registration. In fact, the code required in the exporting apartment reduces to a single line.

1HRESULT RegisterMyInterface(IMyInterface* pmi) {
2  CComGITPtr<IMyInterface> git(pmi);
3  // creates GIT or gets ref to existing GIT
4  // registers interface in GIT
5  // retrieves cookie and caches it
6
7    // ... interface removed from GIT when git goes out of scope
8}

In the importing apartment, clients that want to use the registered interface pointer simply use the CopyTo method CComGITPtr provides:

1HRESULT CopyTo(T** pp) const

This can be used in code like this:

1HRESULT ReadMyInterface(const CComGITPtr<IMyInterface>& git) {
2    IMyInterface* pmi = NULL;
3    HRESULT hr = git.CopyTo(&pmi);
4    ATLASSERT(SUCCEEDED(hr));
5
6    //... use pmi as usual
7}

A potentially dangerous race condition occurs if you’re not careful using CComGITPtr. Remember that the entire reason for having a GIT is to make an interface accessible from multiple threads. This means that you will be passing GIT cookies from an exporting apartment that is not synchronized with code in the importing apartment that will be using the associated registered interface. If the lifetime of the CComGITPtr is not carefully managed, the importing apartment could easily end up with an invalid cookie. Here’s the scenario:

 1void ThreadProc(void*);    // forward declaration
 2HRESULT RegisterInterfaceAndFork(IMyInterface* pmi) {
 3    CComGITPtr<IMyInterface> git(pmi); // interface registered
 4    // create worker thread and pass CComGITPtr instance
 5    ::_beginthread(ThreadProc, 0, &git);
 6}
 7void ThreadProc(void* pv)
 8{
 9    CComGITPtr<IMyInterface>* pgit =
10        (CComGITPtr<IMyInterface>*)pv;
11    IMyInterface* pmi = NULL;
12    HRESULT hr = pgit->CopyTo(&pmi);
13    // ... do some work with pmi
14}

The trouble with this code is that the RegisterInterfaceAndFork method could finish before the THReadProc retrieves the interface pointer using CopyTo. This means that the git variable will go out of scope and unregister the IMyInterface pointer from the GIT too early. You must employ some manner of synchronization, such as WaitForSingleObject, to guard against problems like these.

In general, CComGITPtr shouldn’t be used as a local variable. Its intended use is as a member variable or global. In those cases, the lifetime of the CComGITPtr object is automatically controlled by the lifetime of the object that contains it, or the lifetime of the process.

The CAutoPtr and CAutoVectorPtr Smart Pointer Classes

CComPtr was presented as a smart pointer class for managing a COM interface pointer. ATL 8 provides a related set of classes for managing pointers to instances of C++ classes, as opposed to CComPtr’s management of interface pointers to COM coclasses. These classes provide a useful encapsulation of the operations required to properly manage the memory resources associated with a C++ object. CAutoPtr, CAutoVectorPtr, CAutoPtrArray, and CAutoPtrList are all defined in atlbase.h.

The CAutoPtr and CAutoVectorPtr Classes

The CAutoPtr template class wraps a C++ object created with the new operator. The class holds a pointer to the encapsulated object as its only state, and exposes convenient methods and operators for controlling the ownership, lifetime, and state of the internal C++ object. CAutoPtr is used in code like this:

1STDMETHODIMP CMyClass::SomeFunc() {
2    CFoo* pFoo = new Foo();        // instantiate C++ class
3    CAutoPtr<CFoo> spFoo(pFoo);    // take ownership of pFoo
4    spFoo->DoSomeFoo();
5    // ... do other things with spFoo
6}   // CAutoPtr deletes pFoo instance
7    // when spFoo goes out of scope

This simple example demonstrates the basic usage pattern of CAutoPtr: Create an instance of a C++ class, transfer ownership of the pointer to CAutoPtr, operate on the CAutoPtr object as if it were the original C++ class, and let CAutoPtr destroy the encapsulated object or reclaim ownership of the pointer. Although this behavior is similar to that of the Standard C++ class auto_ptr, that class throws exceptions, whereas ATL’s CAutoPtr does not. ATL developers sometimes do not link with the CRT, so the exception support required by auto_ptr would not be available.

CAutoVectorPtr enables you to manage a pointer to an array of C++ objects. It operates almost identically to CAutoPtr; the principal difference is that vector new[] and vector delete[] are used to allocate and free memory for the encapsulated objects. The comments in the sections that follow are written in terms of CAutoPtr, although most apply equally well to both CAutoPtr and CAutoVectorPtr.

Constructors and Destructor

CAutoPtr provides four constructors to initialize new instances. The first constructor simply creates a CAutoPtr instance with a NULL-encapsulated pointer.

 1CAutoPtr()  : m_p( NULL ) { }
 2
 3template< typename TSrc >
 4CAutoPtr( CAutoPtr< TSrc >& p ) {
 5    m_p = p.Detach();  // Transfer ownership
 6}
 7
 8CAutoPtr( CAutoPtr< T >& p ) {
 9    m_p = p.Detach();  // Transfer ownership
10}
11
12explicit CAutoPtr( T* p ) : m_p( p ) { }

To use this class to do any meaningful work, you have to associate a pointer with the instance using either the Attach method or one of the assignment operators (these are discussed shortly). The third constructor enables you to initialize with another CAutoPtr instance. This simply uses the Detach method to transfer ownership of the object encapsulated from the CAutoPtr instance passed in to the instance being constructed. The fourth constructor also transfers ownership of an object pointer, but using the object pointer directly as the constructor parameter. The second constructor is an interesting one. It defines a templatized constructor with a second type parameter, TSrc. This second template parameter represents a second type from which the CAutoPtr instance can be initialized. The type of the encapsulated pointer within CAutoPtr instance was established at declaration by the CAutoPtr class’s template parameter T, as in this declaration:

1CAutoPtr<CAnimal> spAnimal(pAnimal);

Here, CAnimal is the declared type of the encapsulate m_p object pointer. So, how is it that we have a constructor that enables us to initialize this pointer to a pointer of a different type? The answer is quite simple. Just as C++ allows pointers to instances of base types to be initialized with pointers to instances of derived types, the fourth CAutoPtr constructor allows instances of CAutoPtr<Base> to be initialized with instances of CAutoPtr<Derived>, as in the following:

1class CAnimal { ... };
2class CDog : public CAnimal { ... };
3//
4...
5CDog* pDog = new CDog();
6CAutoPtr<CAnimal> spAnimal(pDog);

The CAutoPtr destructor is invoked whenever the instance goes out of scope. It leverages the Free method to release the memory associated with the internal C++ class object.

1~CAutoPtr()  {
2    Free();
3}
4void Free()  {
5    delete m_p;
6    m_p = NULL;
7}

CAutoPtr Operators

CAutoPtr defines two assignment operators:

 1template< typename TSrc >
 2CAutoPtr< T >& operator=( CAutoPtr< TSrc >& p ) {
 3    if(m_p==p.m_p) {
 4        ATLASSERT(FALSE);
 5    } else {
 6        Free();
 7        Attach( p.Detach() ); // Transfer ownership
 8    }
 9    return( *this );
10}
11
12CAutoPtr< T >& operator=( CAutoPtr< T >& p ) {
13    if(*this==p) {
14        if(this!=&p) {
15            ATLASSERT(FALSE);
16            p.Detach();
17        } else {
18        }
19    } else {
20        Free();
21        Attach( p.Detach() ); // Transfer ownership
22    }
23    return( *this );
24}

Both of these operators behave the same. The difference is that the first version is templatized to a second type TSrc. As with the templatized constructor just discussed that accepts a TSrc template parameter, this assignment operator allows for assignment of pointers to instances of base types to be assigned to pointers to instances of derived types. You can take advantage of this flexibility in code such as the following:

 1class CAnimal { ... };
 2class CDog : public CAnimal { ... };
 3// ...
 4
 5// instantiate a CAnimal
 6CAutoPtr<CAnimal> spAnimal(new CAnimal());
 7
 8// instantiate a CDog
 9CAutoPtr<CDog> spDog(new CDog());
10// CAnimal instance freed here
11spAnimal = spDog;
12
13// ... CDog instance will be freed when spAnimal
14// goes out of scope

Regardless of whether you assign a CAutoPtr instance to the same type or to a derived type, the assignment operator first checks for some important misuses (such as multiple CAutoPtr objects pointing to the same underlying C++ object) and then calls the Free method to delete the encapsulated instance before taking ownership of the new instance. The call to p.Detach ensures that the instance on the right side of the assignment does not also try to delete the same object.

CAutoPtr also defines a cast operator and overloads the member access operator (->):

1operator T*() const {
2    return( m_p );
3}
4T* operator->() const {
5    ATLASSERT( m_p != NULL );
6    return( m_p );
7}

Both operators simply return the value of the encapsulated pointer. The member access operator exposes the public member functions and variables of the encapsulated object, so you can use instances of CAutoPtr more like the encapsulated type:

1class CDog {
2public:
3    void Bark() {}
4    int m_nAge;
5};
6CAutoPtr<CDog> spDog(new Dog);
7spDog->Bark();
8spDog->m_nAge += 5;

Finally, CAutoPtr defines operator== and operator!= to do comparisons between two CAutoPtr objects.

1bool operator!=(CAutoPtr<T>& p) const { return !operator==(p); }
2bool operator==(CAutoPtr<T>& p) const { return m_p==p.m_p; }

CAutoVectorPtr

CAutoVectorPtr differs from CAutoPtr in only a few ways. First, CAutoVectorPtr does not define a constructor that allows the initialization of an instance using a derived type. Second, CAutoVectorPtr defines an Allocate function to facilitate construction of a collection of encapsulated instances.

1bool Allocate( size_t nElements ) {
2    ATLASSERT( m_p == NULL );
3    ATLTRY( m_p = new T[nElements] );
4    if( m_p == NULL ) {
5        return( false );
6    }
7
8    return( true );
9}

This method simply uses the vector new[] to allocate and initialize the number of instances specified with the nElements parameter. Here’s how you might apply this capability:

1class CAnimal { public: void Growl() {} };
2// each instance is of type CAnimal
3CAutoVectorPtr<CAnimal> spZoo;
4// allocate and initialize 100 CAnimal's
5spZoo.Allocate(100);

Note that CAutoVectorPtr does not overload the member access operator (->), as did CAutoPtr. So, you cannot write code like this:

1spZoo->Growl();    // wrong! can't do this => doesn't make sense

Of course, such an operation doesn’t even make sense because you’re not specifying which CAnimal instance should growl. You can operate on the encapsulated instances only after retrieving a specific one from the encapsulated collection. It’s not clear why the ATL team didn’t overload operator[] to provide a convenient arraylike syntax for accessing individual instances contained in a CAutoVectorPtr instance. So, you have to write code such as the following to get at members of a particular encapsulated instance:

1((CAnimal*)spZoo)[5].Growl();

I find myself underwhelmed that the ATL team didn’t simply overload operator[] to provide a more convenient arraylike syntax for accessing individual members of the collection. But, hey, it’s their worldI’m just livin’ in it.

CAutoVectorPtr has another limitation, but, unfortunately, I can’t point the finger at Microsoft for this one. A consequence of CAutoVectorPtr using the vector new[] to allocate the collection of encapsulated objects is that only the default constructor of the encapsulated type is invoked. If the class you want CAutoVectorPtr to manage defines nondefault constructors and performs special initialization in them, the Allocate function has no way to call these constructors. This also means that your class must define a default (parameterless) constructor, or you won’t even be able to use your class with CAutoVectorPtr. So, if we change our CAnimal, this code won’t compile:

 1class CAnimal {
 2public:
 3CAnimal(int nAge) : m_nAge(nAge) {}
 4        void Growl() {}
 5private:
 6int m_nAge;
 7}
 8
 9CAutoVectorPtr<CAnimal> spZoo;
10spZoo.Allocate(100); // won't compile => no default constructor

The final difference CAutoVectorPtr has with CAutoPtr is in the implementation of the destructor. Allocate used the vector new[] to create and initialize the collection of encapsulated instances, so the destructor must match this with a vector delete[] operation. In C++, you must always match the vector allocation functions this way; otherwise, bad things happen. This ensures that the destructor for each object in the collection is run and that all the associated memory for the entire collection is properly released. Exactly what happens if this regimen isn’t followed is compiler specific (and also compiler setting specific). Some implementations corrupt the heap immediately if delete[] is not used; others invoke the destructor of only the first object in the collection. CAutoVectorPtr does the right thing for you if you have let it handle the allocation via the Allocate member function. However, you can get yourself into trouble by improperly using Attach, with code such as the following:

1class CAnimal {};
2// allocate only a single instance
3CAnimal* pAnimal = new Animal;
4CAutoVectorPtr<CAnimal> spZoo;
5// wrong, wrong!!! pAnimal is not a collection
6spZoo.Attach(pAnimal)

In this code, the original pointer to the C++ instance was allocated using new instead of vector new[]. So, when spZoo goes out of scope and the destructor runs, it will eventually call vector delete[]. That will be bad. In fact, it will throw an exception, so be careful.

ATL Memory Managers

A Review of Windows Memory Management

Applications use memory for almost everything they do. In Windows, memory can be allocated from three principle places: the thread’s stack, memory-mapped files, and heaps. Memory-mapped files are more specialized, so we don’t discuss them further here. The stack is used to allocate local variables because their size is known at compile time and their allocation must be as efficient as possible. Allocating and deallocating storage from the stack involves merely incrementing and decrementing the stack pointer by the appropriate amount. Dynamic memory, however, is allocated and freed as the program runs, based on changing characteristics within the application. Instead of being allocated from the thread’s stack, dynamic memory comes from pools of storage known as heaps. A heap is an independently managed block of memory that services dynamic memory allocation requests and reclaims memory that an application no longer uses. Typically, heaps expose APIs for creating the heap, destroying the heap, allocating a block of memory within the heap, and returning a block of memory to the heap. The precise algorithms employed in coordinating these tasks constitute what is commonly termed the heap manager. In general, heap managers implement various schemes for managing resources for specialized circumstances. The heap manager functions exposed for applications often reflect some of the differences that make one particular type of heap suitable over another for a particular circumstance.

In Windows, each process creates a default heap at initialization. Applications use Win32 functions such as HeapCreate, HeapAlloc, and HeapFree to manage the heap and blocks of data within the default heap. Because many Windows functions that can be called from multiple applications use this heap, the default heap is implemented to be thread safe. Access to the default heap is serialized so that multiple simultaneous threads accessing the heap will not corrupt it. Older versions of Windows used functions such as LocalAlloc and GlobalAlloc to manipulate the heap, but these functions are now deprecated. They run slower and offer fewer features than the HeapXXX suite.

Applications that link to the C-runtime library (which ATL projects now do by default) have access to another heap simply known as the CRT heap. The memory-management functions the CRT heap manager exposes are likely the most recognizable to the general C/C++ community because they are part of the C standard. With the CRT heap, functions such as malloc are used to obtain storage from the heap; free is used to return storage to the heap.

An application might need to use different heaps for various reasons, such as with specialized management requirements. For instance, COM introduces an additional set of complexities to the general problem of memory management. Memory addresses are allocated on a per-process basis, so a process cannot directly access data stored in another process. Yet COM allows data to be marshaled between processes. If a method call is remoted so that a client expects an [out] parameter from the object, the memory for that [out] parameter will be allocated in one process (the object’s), and used and freed in another process (the client’s). Clearly, a conventional heap has no way to straddle process boundaries to associate allocations in one process with free operations in another. The COM task allocator lives to provide this very service. Part of the COM programming conventions is that when allocating memory blocks that will be shared across a COM interface, that memory must be allocated by calling CoTaskMemAlloc and must be freed by the corresponding CoTaskMemFree. By agreeing on these standardized functions, the automatically generated proxy-stub code can properly allocate and free memory across COM boundaries. COM’s remoting infrastructure does all the dirty work needed to create the illusion of a single heap that spans processes.

Several other reasons exist for managing memory with different heaps. Components that are allocated from separate heaps are better isolated from one another, which could make the heaps less susceptible to corruption. If objects will be accessed close together in timesay, within the same functionit is desirable for those objects to live close together in memory, which can result in fewer page faults and a marked improvement in overall performance. Some applications choose to implement custom, specialized memory managers that are tuned to specific requirements. Using separate heaps also could allow the application to avoid the overhead associated with synchronizing access to a single heap. As previously mentioned, a Win32 process’s default heap is thread safe because it expects to be accessed simultaneously from multiple threads. This leads to thread contention because each heap access must pass through thread-safe interlocked operations. Applications can devote one heap to each thread and eliminate the synchronization logic and thread contention.

ATL simplifies the use of heaps through a series of concrete heap implementations that wrap Windows API functions and through an abstract interface that allows these implementations to be used polymorphically in other ATL classes that use memory resources.

The IAtlMemMgr Interface

The atlmem.h header file expresses the generic memory-management pattern through the definition of the IAtlMemMgr interface.

1__interface IAtlMemMgr {
2public:
3    void* Allocate( size_t nBytes ) ;
4    void Free( void* p ) ;
5    void* Reallocate( void* p, size_t nBytes ) ;
6    size_t GetSize( void* p ) ;
7};

The four simple functions defined on this interface provide most of the dynamic memory functionality required in typical applications. Allocate reserves a contiguous region of space nBytes in size within the heap. Free takes a pointer to a memory block retrieved from Allocate and returns it to the heap so that it will be available for future allocation requests. The Reallocate method is useful when an allocated block is not large enough to accommodate additional data and it is more practical and/or efficient to grow the existing block than to allocate a new, larger one and copy the contents. Finally, GetSize accepts a pointer to a block obtained from Allocate and returns the current size of the block in bytes.

Many ATL classes are designed to support pluggable heap implementations by performing all their memory-management functions through an IAtlMemMgr reference. Developers can provide custom implementations of IAtlMemMgr and use them with ATL. This provides a great deal of flexibility in optimizing the performance of these classes to suit specific application requirements. ATL Server makes heavy use of IAtlMemMgr in processing SOAP requests and in stencil processing. Additionally, we’ve already seen how CStringT allows developers to supply an IAtlMemMgr implementation to optimize string-handling performance.

The Memory Manager Classes

Although it is useful to abstract memory management behind an interface to facilitate custom heap implementations, most applications don’t need a high degree of sophistication in these implementations to build efficient components. Indeed, you can realize many of the benefits of multiple heaps with simple heap implementations. To that end, ATL provides five concrete implementations of IAtlMemMgr that you can use as is in many circumstances.

CComHeap is defined in atlcommem.h as follows:

 1class CComHeap :
 2    public IAtlMemMgr {
 3// IAtlMemMgr
 4public:
 5    virtual void* Allocate( size_t nBytes ) {
 6#ifdef _WIN64
 7        if( nBytes > INT_MAX ) { return( NULL ); }
 8#endif
 9        return( ::CoTaskMemAlloc( ULONG( nBytes ) ) );
10    }
11    virtual void Free( void* p ) {
12        ::CoTaskMemFree( p );
13    }
14    virtual void* Reallocate( void* p, size_t nBytes ) {
15#ifdef _WIN64
16        if( nBytes > INT_MAX ) { return( NULL ); }
17#endif
18        return( ::CoTaskMemRealloc( p, ULONG( nBytes ) ) );
19    }
20    virtual size_t GetSize( void* p ) {
21        CComPtr< IMalloc > pMalloc;
22        ::CoGetMalloc( 1, &pMalloc );
23        return( pMalloc->GetSize( p ) );
24    }
25};

As you can see, this class is merely a very thin wrapper on top of the COM task allocator API functions. Allocate simply delegates to CoTaskMemAlloc, and Free delegates to CoTaskMemFree. In fact, all five of the stock memory managers implement IAtlMemMgr in a similar manner; the prime difference is the underlying functions to which the managers delegate. Table 3.2 summarizes which heap-management functions are used for each of the ATL memory managers.

Table 3.2. Heap Functions Used in ATL Memory Managers

Memory Manager Class

Heap Functions Used

CComHeap

CoTaskMemAlloc, CoTaskMemFree, CoTaskMemRealloc, IMalloc::GetSize

CCRTHeap

malloc, free, realloc, _msize

CLocalHeap

LocalAlloc, LocalFree, LocalReAlloc, LocalSize

CGlobalHeap

GlobalAlloc, GlobalFree, GlobalReAlloc, GlobalSize

CWin32Heap

HeapAlloc, HeapFree, HeapReAlloc, HeapSize

The CCRTHeap uses memory from the CRT heap, whereas CLocalHeap and CGlobalHeap both allocate memory from the process heap. The LocalXXX and GlobalXXX functions in the Win32 API exist now mostly for backward compatibility. You shouldn’t really use them in new code anymore, so we don’t discuss them further.

The CWin32Heap class is a bit different from the other heap classes in a couple important respects. Whereas the other memory managers allocate storage from the process heap, CWin32Heap requires that a valid HANDLE to a heap be created before using its IAtlMemMgr implementation. This gives the developer a bit more control over the details of the underlying heap that will be used, albeit with a bit more complexity. CWin32Heap supplies three constructors for initializing an instance:

 1CWin32Heap()  :    m_hHeap( NULL ), m_bOwnHeap( false ) { }
 2CWin32Heap( HANDLE hHeap )  :
 3    m_hHeap( hHeap ),
 4    m_bOwnHeap( false ) {
 5    ATLASSERT( hHeap != NULL );
 6}
 7CWin32Heap( DWORD dwFlags, size_t nInitialSize,
 8    size_t nMaxSize = 0 ) :
 9    m_hHeap( NULL ),
10    m_bOwnHeap( true ) {
11    ATLASSERT( !(dwFlags&HEAP_GENERATE_EXCEPTIONS) );
12    m_hHeap = ::HeapCreate( dwFlags, nInitialSize, nMaxSize );
13    if( m_hHeap == NULL ) { AtlThrowLastWin32(); }
14}

The first constructor initializes a CWin32Heap instance with no associated heap. The second constructor initializes the instance with a handle to an existing heap obtained from a previous call to the Win32 HeapCreate API. Note that the m_bOwnHeap member is set to false in this case. This member tracks whether the CWin32Heap instance owns the underlying heap. Thus, when the second constructor is used, the caller is still responsible for ultimately calling HeapDestroy to get rid of the heap later. The third constructor is arguably the simplest to use because it directly accepts the parameters required to create a heap and invokes HeapCreate automatically. The dwFlags parameter is a bit field that allows two different flags to be set. One of the two flags, HEAP_GENERATE_EXCEPTIONS, can be given to the underlying HeapCreate call to indicate that the system should raise an exception upon function failure. However, the code asserts if this flag is specified because the ATL code base isn’t prepared for system exceptions to be thrown if an allocation fails. The other flag, HEAP_NO_SERIALIZE, relates to the synchronization options with heaps, discussed a bit earlier in this section. If this flag is specified, the heap is not thread safe. This can improve performance considerably because interlocked operations are no longer used to gain access to the heap. However, it is the programmer’s responsibility to ensure that multiple threads will not access a heap created with this flag set. Otherwise, heap corruption is likely to occur. The nInitialSize parameter indicates how much storage should be reserved when the heap is created. You can use the nMaxSize parameter to specify how large the heap should be allowed to grow.

CWin32Heap also defines Attach and Detach operations to associate an existing heap with a CWin32Heap instance:

 1void Attach( HANDLE hHeap, bool bTakeOwnership ) {
 2    ATLASSERT( hHeap != NULL );
 3    ATLASSERT( m_hHeap == NULL );
 4    m_hHeap = hHeap;
 5    m_bOwnHeap = bTakeOwnership;
 6}
 7HANDLE Detach() {
 8    HANDLE hHeap;
 9
10    hHeap = m_hHeap;
11    m_hHeap = NULL;
12    m_bOwnHeap = false;
13
14    return( hHeap );
15}

Attach accepts a handle to a heap and a Boolean flag indicating whether the caller is transferring ownership of the heap to the CWin32Heap instance. This governs whether the destructor will destroy the heap. Detach simply surrenders ownership of the encapsulated heap by flipping the m_bOwnHeap member to FALSE and returning the handle to the caller. Note that Attach simply overwrites the existing heap handle stored in the CWin32Heap. If the class already held a non-NULL HANDLE, there would be no way to free that heap after the Attach is performed. As a result, you have a memory leakand a really big one, at that. If you thought leaking memory from an object was bad, trying leaking entire heaps at a time! You might wonder at first why the Attach method doesn’t simply destroy the existing heap before overwriting the internal handle. After all, CComVariant::Attach and CComSafeArray::Attach were shown earlier clearing their encapsulated data before attaching to a new instance. The difference here is that even if the CWin32Heap instance owns the heap (m_bOwnHeap is TRUE), it has no knowledge of what live objects out there have been allocated from that heap. Blindly destroying the existing heap would yank memory from any number of objects, which could be disastrous. You simply have to be careful. Here’s the kind of code you want to avoid:

 1// create an instance and allocate a heap
 2CWin32Heap heap(0,        // no exceptions, use thread-safe access
 3                4000,     // initial size
 4                0);       // no max size => heap grows as needed
 5
 6// manually create a second heap
 7HANDLE hHeap = ::HeapCreate(0, 5000, 0);
 8
 9// this is gonna get you in a "heap" of trouble!
10heap.Attach(hHeap, false /* same result if true */ );

Custom memory management commonly is used in string processing. Applications that allocate, free, and resize strings frequently can often tax memory managers and negatively impact performance. Multithreaded applications that do a lot of string processing can exhibit reduced performance because of thread contention for heap allocation requests. Moreover, heaps that service multithreaded applications can provide slower access because synchronization locks of some sort must be employed to ensure thread safety. One tactic to combat this is to provide a per-thread heap so that no synchronization logic is needed and thread contention does not occur.

We show an example of a specialized heap for string allocations using CWin32Heap and ATL’s new CStencil class. This class is discussed in detail in later chapters when we cover building web applications with ATL Server. For now, recall from the discussion of web application development in Chapter 1, “Hello, ATL,” that ATL produces web pages by processing stencil response files and rendering HTML-based text responses. This involves a great deal of string parsing and processing, and CStencil bears a lot of this burden. Its constructor enables you to pass in a custom memory manager to be used in all its string parsing. The following code demonstrates how to create a per-thread heap manager to be used for stencil processing:

 1DWORD g_dwTlsIndex;    // holds thread-local storage slot index
 2// g_dwTlsIndex = ::TlsAlloc() performed in other
 3// initialization code
 4
 5// Create a private heap for use on this thread
 6// only => no synchronized access
 7CWin32Heap* pHeap = new CWin32Heap(HEAP_NO_SERIALIZE, 50000);
 8
 9// Store the heap pointer in this thread's TLS slot
10::TlsSetValue(g_dwTlsIndex, reinterpret_cast<void*>(
11static_cast<IAtlMemMgr*>(pHeap)));
12
13// ...
14
15// Retrieve the heap pointer from TLS
16pHeap = (IAtlMemMgr*)::TlsGetValue(g_dwTlsIndex);
17
18// Create a new CStencil instance that uses the private heap
19CStencil* pStencil = new CStencil(pHeap);

Notice the extra layer of casting when storing the heap pointer in the TLS slot. You need to hold on to the original CWin32Heap pointer with the concrete type because IAtlMemMgr doesn’t have a virtual destructor. If you just had an IAtlMemMgr* to call delete on, the actual CWin32Heap destructor would not get called. That extra layer of casting is to make sure that you get the correct interface pointer converted to void* before storing it in the TLS. It’s probably not strictly necessary in the current version of ATL, but if the heap implementation has multiple base classes, the cast to void* could cause some serious trouble.

Summary

ATL provides a rich set of classes for manipulating the data types COM programmers frequently use. The CComVariant class provides practically the same benefits as the CComBSTR class, but for VARIANT structures. If you use a VARIANT structureand you will need to use one sooner or lateryou should instead use the ATL CComVariant smart VARIANT class. You’ll have far fewer problems with resource leaks.

SAFEARRAYs have a number of specialized semantics as well. CComSafeArray was shown to be a useful template class for managing both single- and multidimensional SAFEARRAY``s. As with other managed resources, however, take care when dealing with ``CComSafeArray because the compiler cannot always tell you if you’ve written code that will result in a memory leak.

The CComPtr, CComQIPtr, and CComGITPtr smart pointer classes ease, but do not totally alleviate, the resource management needed for interface pointers. These classes have numerous useful methods that let you write more application code and deal less with the low-level resource-management details. You’ll find smart pointers most useful when you’re using interface pointers with exception handling.

Finally, you can control memory management in ATL with IAtlMemMgr and five concrete implementations that are supplied: CWin32Heap, CComHeap, CCRTHeap, CLocalHeap, and CGlobalHeap. You can program ATL classes such as CString to use memory from these heaps or even from a custom implementation of IAtlMemMgr, to provide a high degree of control and facilitate any number of performance optimizations.