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