previous page
next page

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:

CComPtr<IUnknown>     punk;
CComPtr<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:

CComPtr<IUnknown>       punk = /* Init to some IUnknown* */ ;
CComQIPtr<INamedObject> pno = punk; // Calls punk->QI
                                    // (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:

template <class T>                                
class CComPtrBase {                               
    ...                                           
    T* p;                                         
};                                                
                                                  
template <class T>                                
class CComPtr : public CComPtrBase<T>             
{ ... };                                          
                                                  
template <class T, const IID* piid = &__uuidof(T)>
class CComQIPtr : public CComPtr<T>               
{ ... };                                          

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:

CComPtr<IUnknown> punk;    // Smart IUnknown*
CComPtr<INamedObject> pno; // Smart INamedObject*

CComQIPtr<INamedObject> pno;
CComQIPtr<INamedObject, &__uuidof(INamedObject)> pno;
CComQIPtr<INamedObject, &IID_INamedObject> pno;

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

CComPtrBase()     { p = NULL; }                         
CComPtrBase(T* p) { if ((p = lp) != NULL) p->AddRef(); }
~CComPtrBase()    { if (p) p->Release(); }              
                                                        
CComPtr(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:

CComQIPtr(T* lp)  :                     
    CComPtr<T>(lp)                      
    {}                                  
                                        
CComQIPtr(const CComQIPtr<T,piid>& lp) :
    CComPtr<T>(lp.p)                    
    {}                                  

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

CComQIPtr(IUnknown* lp)                                        
    { 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:

void func (IUnknown* punk) {
    CComQIPtr<INamedObject> pno (punk);
    if (pno) {
        // Can call SomeMethod because the QI worked
        pno->SomeMethod ();
    }
}

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:

// CComPtr assignment operators                          
T* operator=(T* lp);                                     
template <typename Q> T* operator=(const CComPtr<Q>& lp);
T* 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:

CComPtr< IFoo > fooPtr = this;
CComPtr< IBar > barPtr;

barPtr = 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.

// CComQIPtr assignment operators    
T* operator=(T* lp);                 
T* operator=(const CComQIPtr<T>& lp);
T* 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:

// Member variable holding object
CComQIPtr<IExpectedInterface> m_object;

STDMETHODIMP put_Object (IUnknown* punk) {
                      // Releases current object, if any, and
    m_object = punk;  // queries for the expected interface
    if (!m_object)
        return E_UNEXPECTED;
    return S_OK;
}

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.

HRESULT CoCreateInstance (REFCLSID rclsid,                  
                          LPUNKNOWN pUnkOuter = NULL,       
                          DWORD dwClsContext = CLSCTX_ALL) {
    ATLASSERT(p == NULL);                                   
    return ::CoCreateInstance(rclsid, pUnkOuter,            
        dwClsContext, __uuidof(T), (void**)&p);             
}                                                           
                                                            
HRESULT CoCreateInstance (LPCOLESTR szProgID,               
                          LPUNKNOWN pUnkOuter = NULL,       
                          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.

ISpeaker* pSpeaker;
HRESULT hr =
 ::CoCreateInstance (__uuidof (Demagogue), NULL, CLSCTX_ALL,
                     __uuidof (ISpeaker_, (void**) &pSpeaker);
... Use the interface
pSpeaker->Release () ;


CComPtr<ISpeaker> pSpeaker;
HRESULT hr = pSpeaker.CoCreateInstance (__uuidof (Demogogue));
... 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:

T& 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.

[2] ATLENSURE asserts if assertions are turned on and throws either a C++ exception (if exceptions are enabled) or a Windows Structured Exception (if C++ exceptions are disabled) if the condition is false.

To maintain the semblance of a pointer, taking the address of a smart pointer objectthat 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**.

T** 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.

STDMETHODIMP SomeClass::UpdateObject (
    /* [in, out] */ IExpected** ppExpected);

CComPtr<IExpected> pE = /* Initialize to some value */ ;

pobj->UpdateObject (&pE); // Asserts in debug build because
                          // pE is non-NULL

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

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

int main( ) {
    HRESULT hr = CoInitialize( NULL );
    If (FAILED(hr)) return 1;  // Something is seriously wrong

    CComPtr<IUnknown> punk = /* Initialize to some object */ ;
    ...
    punk.Release( ); // Must Release before CoUninitialize!

    CoUninitialize( );
}

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:

void Release() {          
     T* pTemp = p;        
     if (pTemp) {         
         p = NULL;        
         pTemp->Release();
     }                    
}                         

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]

[3] Thanks go to Jim Springfield for pointing this out.

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.

punk->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:

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

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

template <class T>                          
class _NoAddRefReleaseOnCComPtr : public T {
    private:                                
        STDMETHOD_(ULONG, AddRef)()=0;      
        STDMETHOD_(ULONG, Release)()=0;     
};                                          

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:

error C2248: 'Release' : cannot access private member declared
  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.

HRESULT CopyTo(T** ppT) {             
    ATLASSERT(ppT != NULL);           
    if (ppT == NULL) return E_POINTER;
    *ppT = p;                         
    if (p) p->AddRef();               
    return S_OK;                      
}                                     

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:

STDMETHODIMP SomeClass::get_Object(
/* [out] */ IExpected** ppExpected) {
    // Interface saved in member m_object
    // of type CComPtr<IExpected>

    // Correctly AddRefs pointer
    return m_object.CopyTo (ppExpected) ;
}

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

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

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:

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

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

STDMETHODIMP SomeClass::put_Object (
    /* [in] */ IExpected* pExpected);

// Interface saved in member m_object of type CComPtr<IExpected>
// Correctly does not AddRef pointer!
pObj->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.

T* 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:

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

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:

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

STDMETHODIMP SomeClass::get_Object (
  /* [out] */ IExpected** ppObject);

void VerboseGetOption () {
  IExpected* p;
  pObj->get_Object (&p) ;

  CComPtr<IExpected> pE;
  pE.Attach (p); // Destructor now releases the interface pointer
  // Let the exceptions fall where they may now!!!
  CallSomeFunctionWhichThrowsExceptions();
}

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.

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

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

CComPtr<IFoo> pfoo = /* Initialize to some IFoo */
IBar* pbar;

// We specify an IBar variable so the method queries for IID_IBar
HRESULT hr = pfoo.QueryInterface(&pBar);

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

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

bool SameObjects(IUnknown* punk1, IUnknown* punk2) {
    CComPtr<IUnknown> p (punk1);
    return p.IsEqualObject (punk2);
}

IUnknown* punk1 = NULL;
IUnknown* punk2 = NULL;
ATLASSERT (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.

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

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

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

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

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

CComPtr<IFoo> pFoo;
// Tests for pFoo.p == NULL using operator!
if (!pFoo)        {...}
// Tests for pFoo.p == NULL using operator==
if (pFoo == NULL) {...}
// Converts pFoo to T*, then compares to NULL
if (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 VARIANTs, 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:

// component IDL file
[
    object,
    uuid(2F6C88D7-C2BF-4933-81FA-3FBAFC3FC34B),
    dual,
]
interface ICalc : IDispatch {
    [id(1)] HRESULT Add([in] DOUBLE Op1,
        [in] DOUBLE Op2, [out,retval] DOUBLE* Result);
};

// client.cpp
HRESULT CallAdd(IDispatch* pdisp) {
  // Get the DISPID
  LPOLESTR pszMethod = OLESTR("Add");
  DISPID dispid;
  hr = pdisp->GetIDsOfNames(IID_NULL,
                            &pszMethod,
                            1,
                            LOCALE_SYSTEM_DEFAULT,
                            &dispid);

  if (FAILED(hr))
       return hr;

  // Set up the parameters
  DISPPARAMS dispparms;
  memset(&dispparms, 0, sizeof(DISPPARAMS));
  dispparms.cArgs = 2;

  // Parameters are passed right to left
  VARIANTARG rgvarg[2];
  rgvarg[0].vt = VT_R8;
  rgvarg[0].dblVal = 6;
  rgvarg[1].vt = VT_R8;
  rgvarg[1].dblVal = 7;

  dispparms.rgvarg = &rgvarg[0];

  // Set up variable to hold method return value
  VARIANTARG vaResult;
  ::VariantInit(&vaResult);

  // Invoke the method
  hr = pdisp->Invoke(dispid,
                     IID_NULL,
                     LOCALE_SYSTEM_DEFAULT,
                     DISPATCH_METHOD,
                     &dispparms,
                     &vaResult,
                     NULL,
                     NULL);

  // vaResult now holds sum of 6 and 7
}

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:

//specialization for IDispatch                            
template <>                                               
class CComPtr<IDispatch> : public CComPtrBase<IDispatch> {
public:                                                   
    CComPtr() {}                                          
    CComPtr(IDispatch* lp)  :                             
        CComPtrBase<IDispatch>(lp) {}                     
    CComPtr(const CComPtr<IDispatch>& lp) :               
        CComPtrBase<IDispatch>(lp.p) {}                   
};                                                        

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:

HRESULT 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:

HRESULT GetProperty(DISPID dwDispID, VARIANT* pVar);
HRESULT 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:

HRESULT GetPropertyByName(LPCOLESTR lpsz, VARIANT* pVar);
HRESULT 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.

HRESULT Invoke0(DISPID dispid, VARIANT* pvarRet = NULL);     
HRESULT Invoke0(LPCOLESTR lpszName, VARIANT* pvarRet = NULL);
HRESULT Invoke1(DISPID dispid, VARIANT* pvarParam1,          
    VARIANT* pvarRet = NULL);                                
HRESULT Invoke1(LPCOLESTR lpszName,                          
        VARIANT* pvarParam1, VARIANT* pvarRet = NULL);       
HRESULT Invoke2(DISPID dispid,                               
        VARIANT* pvarParam1, VARIANT* pvarParam2,            
        VARIANT* pvarRet = NULL);                            
HRESULT Invoke2(LPCOLESTR lpszName,                          
        VARIANT* pvarParam1, VARIANT* pvarParam2,            
        VARIANT* pvarRet = NULL);                            
HRESULT InvokeN(DISPID dispid,                               
        VARIANT* pvarParams, int nParams,                    
        VARIANT* pvarRet = NULL);                            
HRESULT InvokeN(LPCOLESTR lpszName,                          
        VARIANT* pvarParams, int nParams,                    
        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:

HRESULT TheEasyWay( IDispatch *spCalcDisp ) {
  CComPtr< IDispatch > spCalcDisp( pCalcDisp );

  CComVariant varOp1( 6.0 );
  CComVariant varOp2( 7.0 );
  CComVariant varResult;
  HRESULT hr = spCalcDisp.Invoke2( OLESTR( "Add" ),
    &varOp1, &varOp2, &varResult );
  // varResult now holds sum of 6 and 7
}

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

static HRESULT GetProperty(IDispatch* pDisp, DISPID dwDispID,
    VARIANT* pVar);                                          
static HRESULT PutProperty(IDispatch* pDisp, DISPID dwDispID,
    VARIANT* pVar);                                          

Here's an example:

HRESULT GetCount(IDispatch* pdisp, long* pCount) {
    *pCount = 0;
    const int DISPID_COUNT = 1;

    CComVariant v;
    CComPtr<IDispatch>::GetProperty (pdisp, DISPID_COUNT, &v);

    HRESULT hr = v.ChangeType (VT_I4);
    If (SUCCEEDED (hr))
        *pCount = V_I4(&v) ;
    return hr;
}

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:

HRESULT RegisterMyInterface(IMyInterface* pmi, DWORD* pdwCookie) {
  // this is usually a global
  IGlobalInterfaceTable* g_pGIT = NULL;
  HRESULT hr = ::CoCreateInstance(CLSID_StdGlobalInterfaceTable,
                                  NULL,
                                  CLSCTX_INPROC_SERVER,
                                  IID_IGlobalInterfaceTable,
                                  (void**)&g_pGIT);
ATLASSERT(SUCCEEDED(hr));
hr = g_pGIT->RegisterInterfaceInGlobal(pmi,
  __uuidof(pmi), pdwCookie);
return hr;
}

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.

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

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:

template <class T> 
class CComGITPtr   
{                  
    // ...           
    DWORD m_dwCookie;
};                   

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:

CComGITPtr() ;                       
CComGITPtr(T* p);                    
CComGITPtr(const CComGITPtr& git);   
explicit 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 CComGITPtrs 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:

CComGITPtr<T>& operator=(T* p)                    
CComGITPtr<T>& operator=(const CComGITPtr<T>& git)
CComGITPtr<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.

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

HRESULT Revoke() {                                        
    HRESULT hr = S_OK;                                    
    if (m_dwCookie != 0) {                                
        CComPtr<IGlobalInterfaceTable> spGIT;             
        HRESULT hr = E_FAIL;                              
        hr = AtlGetGITPtr(&spGIT);                        
                                                          
        ATLASSERT(spGIT != NULL);                         
        ATLASSERT(SUCCEEDED(hr));                         
        if (FAILED(hr))                                   
            return hr;                                    
                                                           
        hr = spGIT->RevokeInterfaceFromGlobal(m_dwCookie);
        if (SUCCEEDED(hr))                                
        m_dwCookie = 0;                                   
    }                                                     
    return hr;                                            
}                                                         

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:

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

The first version of Attach calls RevokeInterfaceFromGlobal if m_dwCookie is nonzerothat 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.

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

HRESULT RegisterMyInterface(IMyInterface* pmi) {
  CComGITPtr<IMyInterface> git(pmi);
  // creates GIT or gets ref to existing GIT
  // registers interface in GIT
  // retrieves cookie and caches it

    // ... interface removed from GIT when git goes out of scope
}

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

HRESULT CopyTo(T** pp) const

This can be used in code like this:

HRESULT ReadMyInterface(const CComGITPtr<IMyInterface>& git) {
    IMyInterface* pmi = NULL;
    HRESULT hr = git.CopyTo(&pmi);
    ATLASSERT(SUCCEEDED(hr));

    //... use pmi as usual
}

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:

void ThreadProc(void*);    // forward declaration
HRESULT RegisterInterfaceAndFork(IMyInterface* pmi) {
    CComGITPtr<IMyInterface> git(pmi); // interface registered
    // create worker thread and pass CComGITPtr instance
    ::_beginthread(ThreadProc, 0, &git);
}
void ThreadProc(void* pv)
{
    CComGITPtr<IMyInterface>* pgit =
        (CComGITPtr<IMyInterface>*)pv;
    IMyInterface* pmi = NULL;
    HRESULT hr = pgit->CopyTo(&pmi);
    // ... do some work with pmi
}

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.


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