Ccomqiptr Assignment

A Review of Smart Pointers

使用smart pointers的好处:

对象析构时,自动release interface

发生异常时,创建在栈上的对象自动release interface

assignment操作时,旧的interface自动release,新的interface自动AddRef

提供不同的构造函数

可以在大部分用raw pointer的地方使用

ATL提供了2种smart pointer:

CComPtr<>

CComQIPtr<> //可以对不同类型的赋值对象做QueryInterface操作

The CComPtr and CComQIPtr Classes

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


T* p是其唯一的state

Constructors and Destructor

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) { }
CComQIPtr(T* lp) : CComPtr<T>(lp) {} CComQIPtr(const CComQIPtr<T,piid>& lp) : CComPtr<T>(lp.p) {}
CComQIPtr(IUnknown* lp) { if (lp != NULL) lp->QueryInterface(*piid, (void **)&p); }

当QueryInterface不成功时,应该注意到指针==null

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


Initialization

// CComPtr assignment operators T* operator=(T* lp); template <typename Q> T* operator=(const CComPtr<Q>& lp);T* operator=(const CComPtr<T>& lp);
// CComQIPtr assignment operators T* operator=(T* lp); T* operator=(const CComQIPtr<T>& lp);T* operator=(IUnknown* lp);


注意CComQIPTR可以直接对非空interface pointer查询

Object Instantiation Methods

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


例子:

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

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


例子:

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


也可以:

pobj->UpdateObject (&pE.p);


CComPtr and CComQIPtr Resource-Management Operations

注意如果smart pointer夹在CoInitilize()和CoUninitialize()之间,析构函数可能来不及运行

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


上面这个例子使用点操作Release,所以操作之后内部指针=null;在ATL3之前如果使用->Release内部pointer将会释放2次

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

ATL3之后,->操作AddRef和Release将会出现编译错误:

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


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

The CopyTo Method

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


通常,使用CopyTo来制造[out]:

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


注意下面的错误代码:

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

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


例子:

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

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


例子:

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


上述可以避免不必要的AddRef/Release操作

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

例子:

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

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


例子:

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);
bool IsEqualObject(IUnknown* pOther);


例子:

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
HRESULT SetSite(IUnknown* punkParent);
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);


CComPtr Comparison Operators

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

The CComPtr Specialization for IDispatch

//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) {} };

Property Accessor and Mutator Methods

HRESULT GetIDOfName(LPCOLESTR lpsz, DISPID* pdispid);

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


上述两步可以合并:

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

Method Invocation Helper Functions

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


注意使用数组传递参数时,应该倒叙

例子:

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 }
static HRESULT GetProperty(IDispatch* pDisp, DISPID dwDispID, VARIANT* pVar); static HRESULT PutProperty(IDispatch* pDisp, DISPID dwDispID, VARIANT* pVar);


例子:

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 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 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: and . The 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 interface pointer. The second line creates a smart custom interface pointer:

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

The class is a smarter COM interface pointer class that does everything does and more. When you assign to a instance an interface pointer of a different type than the smart pointer, the class calls 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 and classes are similar, with the exception of initialization and assignment. In fact, they're so similar that actually derives from and , in turn, derives from another class, . This latter class defines the actual storage of the underlying raw pointer, and the actual reference-counting operations on that raw pointer. and add constructors and assignment operators. Because of the inheritance relationship between these classes, all the following comments about the class apply equally to the class unless specifically stated otherwise.

The file contains the definition of all three classes. The only state each class maintains is a single public member variable, . This state is defined in the 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 , only) template parameter specifies the type of the smart interface pointer. The second template parameter to the 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 object can be initialized with an interface pointer of the appropriate type. That is, a object can be initialized using an or another object. Using any other type produces a compiler error. The actual implementation of this behavior is in the class. The default constructor initializes the internal interface pointer to . The other constructors initialize the internal interface pointer to the specified interface pointer. When the specified value is non-, the constructor calls the method. The destructor calls the method on a non- interface pointer.

has a special copy constructor that pulls out the underlying raw interface pointer and passes it to the base class, thus guaranteeing proper and 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 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 '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 call might not succeed. The class sets the internal pointer to 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 pointer, but you cannot determine why it fails. The constructor doesn't save the from a failed call.

Initialization

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

  • the current interface pointer when it's non-.

  • the source interface pointer when it's non-.

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

The 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 is interesting. It enables you to assign arbitrary to each other with proper 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 at all if 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 in place for backward compatibility.

The assignment operators are mostly the same. The only one that does any interesting work is the overload that takes an This queries a non- source interface pointer for the appropriate interface to save. You receive a pointer when the calls fail. As with the equivalent constructor, the 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 assignment operator to perform a call. You immediately follow the assignment with a 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 class provides an overloaded method, called , 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 () of the class to instantiate. The second requires the programmatic identifier ( 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 , the normal case, which indicates no aggregation. The class context parameter defaults to , 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 method creates an instance of the specified class. It passes the parameters of the method to the COM API and, additionally, requests that the initial interface be the interface that the smart pointer class supports. (This is the purpose of the expression.) The second overloaded method translates the provided to a 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 COM API. The second uses the smart pointer 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 class defines some operators to make the smart pointer objects act like pointers. For example, when you dereference a pointer using , 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 method kindly asserts (via the macro) when you attempt to dereference a 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 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 objectthat is, invoking 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 , taking the address of an instance returns a . To have a smart pointer class maintain the same pointer semantics as a pointer of type , the method for the class must return a .

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

Note that this operator asserts when you take the address of a non- 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 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 method. This means that you can't wait for the destructor of a object to release the interface pointer when you allocate the object as a global or static variableor even a local variable in . The destructor for global and static variables executes only after the main function exits, long after runs.

You can release the internal interface pointer by assigning to the smart pointer. Alternatively and more explicitly, you can call the 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 method because it uses the dot operator to reference the object. It does not directly call the underlying interface pointer's method, as you might expect. The smart pointer's method calls the underlying interface pointer's method and sets the internal interface pointer to . This prevents the destructor from releasing the interface again. Here is the smart pointer's method:

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

It's not immediately obvious why the method doesn't simply call using its member variable. Instead, it copies the interface pointer member variable into the local variable, sets the member variable to , 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 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.

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 function, not the method, as expected. This left the smart pointer's interface pointer member variable non-, 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 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 and methods inaccessible:

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

The template class derives from the interface being returned. Therefore, it inherits all the methods of the interface. The class then overrides the and 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 method makes an 'ed copy of the interface pointer and places it in the specified location. Therefore, the 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 method to copy a smart pointer to an parameter. An interface pointer must be '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 method. In other words, you cast the smart pointer to its underlying type. Notice that doesn't the pointer it returns:

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

That's because you don't want the 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 instance from the instance to an equivalent raw pointer, use the method. It returns the underlying interface pointer and sets the smart pointer to , ensuring that the destructor doesn't release the interface. The client calling becomes responsible for releasing the interface.

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

You often use when you need to return to a caller an interface pointer that you no longer need. Instead of providing the caller an '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 / 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 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 / calls and is a useful minor optimization:

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

Client code can use the method to assume ownership of a raw interface pointer that it receives as an parameter. The function that provides an 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 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, ), but specifying a different type of pointer for the returned value (for example, ).

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 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 and compare the results. A COM object must always return the same pointer value when asked for its interface. The method expands a little on the COM identity test. It considers two 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 method associates a site object (specified by the parameter) with the object referenced by the internal pointer. The smart pointer must point to an object that implements the interface.

HRESULT SetSite(IUnknown* punkParent);

The 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 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 smart pointer method to end the connection because the pointer is not needed for the . 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 method returns when the interface pointer is . The method returns when the comparison operand is equal to the interface pointer. The 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 method. You have to package all the arguments into structures, build an array of those s, and translate the name of the method to a . 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 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 interface is very similar. Fortunately, ATL provides relief from writing code like this.

provides a specialization for dealing with the 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 , it inherits the typical smart pointer methods. I examine only the ones that differ significantly from those discussed for the and 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 interface. First, you can get the for a property, given its string name, by calling the method:

HRESULT GetIDOfName(LPCOLESTR lpsz, DISPID* pdispid);

When you have the for a property, you can get and set the property's value using the and methods. You specify the of the property to get or set and send or receive the new value in a 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 and methods:

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

Method Invocation Helper Functions

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

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

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

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

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

Each variation expects the 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 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: and . You can use these methods to get and set a property using its , even if you haven't encapsulated the pointer in a .

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 to create an instance of the GIT and obtain an pointer. The exporting apartment then calls to register the interface in the GIT. As a result of the call to , 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 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 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 smart pointer class. This class is defined in 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 . Under the covers, 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. retrieves a reference to the GIT from , 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."

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 member variable to zero. The second constructor accepts an interface pointer. This constructor retrieves an pointer to a global instance of the GIT and calls . The resulting cookie is cached in . The third constructor accepts a reference to an existing instance of . This overload retrieves the interface associated with the passed-in parameter, reregisters it in the GIT to get a second cookie, and stores the new cookie in its own m_ member variable. This leaves the two 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 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 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 method, which takes care of retrieving an pointer and using it to call .

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 instance that has already been initialized, you can use one of the methods to associate a different interface with the instance:

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

The first version of calls if is nonzerothat is, if this is already managing an interface registered in the GIT. It then calls using the new interface 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 method can be used to disassociate the interface from the instance.


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

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 method 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 . 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 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 method could finish before the retrieves the interface pointer using . This means that the variable will go out of scope and unregister the pointer from the GIT too early. You must employ some manner of synchronization, such as , to guard against problems like these.

In general, 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 object is automatically controlled by the lifetime of the object that contains it, or the lifetime of the process.

One thought on “Ccomqiptr Assignment

Leave a Reply

Your email address will not be published. Required fields are marked *