This appendix gives a quick overview of how to use the ATL smart pointer classes, CComPtr and CComQIPtr .
One of the central ideas in COM is that objects manage their own lifetimes. An object is created with a reference count of 1. Whenever a function receives an interface pointer, it must release the pointer after it s done using it. Whenever a function gives away an interface pointer, it must AddRef the pointer. Once the object s reference count goes to zero, the object deletes itself. Reference counts save you from having to figure out which code path is the last to use an object, because your code never explicitly deletes the object. As long as every function manages its own reference counts correctly, everything works.
That s the theory. In practice, getting reference counts right can be rather irritating . It s all too easy to overlook a stray AddRef or Release , and missing just one will result in memory leaks or objects that prematurely delete themselves . This is where smart pointers can make your life easier.
A smart pointer is a C++ class that manages a COM interface pointer, including the reference count. There are various smart pointer classes available. For example, Microsoft Visual C++ defines a _ com_ptr_t class. This book uses the ATL classes, but the principles are the same.
Declare a CComPtr object in the following way.
CComPtr<ISomeInterface> pA; // "ISomeInterface" is any COM interface.
The interface type is an argument to the class template, and the CComPtr object contains a raw interface pointer of that type. The class overloads operator->() and operator & () , so you can access the interface pointer transparently .
hr = GetSomeObject(&pA); // The & operator has the expected behavior. pA->SomeMethod(); // The '->' operator also has the expected behavior.
When the CComPtr object goes out of scope, it automatically releases the interface ” but only if the interface pointer is valid. If the underlying pointer was never initialized in the first place, the CComPtr object safely goes out of scope without calling Release . That means that you can write code that looks like the following.
HRESULT MyFunction() { CComPtr<ISomeInterface> pA; HRESULT hr = GetSomeObject(&pA); // Returns an interface pointer. if (FAILED(hr)) { return hr; // Because pA was not initialized, it is not released. } pA->SomeMethod(); // On exit, pA is released correctly. }
Without smart pointers, if you exit in the middle of a function, you have to carefully work out which pointers to release and which are still NULL . Or else, to avoid that problem, you end up with endlessly nested SUCCEEDED tests. The CComPtr destructor handles it all for you.
The equality and inequality operators are overloaded, and the CComPtr object evaluates to true only when the interface pointer is valid. With this feature, you can use an if test to check the validity of the pointer before trying to dereference it.
if (pControl) // Is this a valid pointer? { pControl->Run(); }
The CComPtr object also provides a helper method for calling CoCreateInstance . Internally, the CComPtr::CoCreateInstance method calls the COM function of the same name with the correct interface identifier (IID) and reasonable default values for the other parameters ( NULL for pUnkOuter and CLSCTX_ALL for dwClsContext , although you can specify other values).
CComPtr<IGraphBuilder> pGraph. pGraph.CoCreateInstance(CLSID_FilterGraph);
Notice that you don t have to coerce pGraph to a void** type, because the smart pointer knows what the type is. This prevents insidious errors caused by passing in the wrong pointer, which can easily happen in regular COM programming because the void** cast circumvents the type checking done by the compiler. The CComPtr::QueryInterface method works similarly.
CComPtr<IMediaControl> pControl. // Query the pGraph pointer for the IMediaControl interface. pGraph.QueryInterface(&pControl);
There may be times when you need to release a smart pointer explicitly, before it goes out of scope ” for example, if the pointer is reinitialized inside a loop. In that case, call the CComPtr::Release method to release the interface. Unlike derefencing the raw pointer and calling Release , the smart pointer s Release method is always safe to call, even when the raw pointer is not valid.
pGraph.Release(); // Releases pGraph.
If you return a pointer from a function, the pointer must have an outstanding reference count according to the rules of COM. (It s the caller s responsibility to release the interface.) Use the Detach method to return a pointer without releasing it.
void GetObject(ISomeInterface **ppI) { CComPtr<ISomeInterface> pI; // Initialize pI to a valid pointer (not shown). // Return the interface pointer to the caller. *ppI = pI.Detach(); // When pI goes out of scope, it does not call Release. // The caller must release the interface. }
This code is functionally equivalent to the following.
void GetObject(ISomeInterface **ppI) { ISomeInterface *pI; // Initialize pI to a valid pointer (not shown). **ppI = pI; // Copy the raw pointer to the [out] parameter. }
Finally, if you want to be really terse, the CComQIPtr class can be used to call QueryInterface in zero lines of code ” the class constructor automatically calls QueryInterface . The constructor argument is the interface pointer that you want to query.
// Query the pGraph pointer for the IMediaControl interface. CComQIPtr<IMediaControl> pControl(pGraph); if (pControl) // Did the QueryInterface call succeed? { pControl->Run(); // OK to dereference the pointer. }
Smart pointers take a little while to get used to, but in the long run they can save you a lot of time debugging reference-count problems. The book ATL Internals, by Brent Rector and Chris Sells, has a good description of the ATL smart pointers. For a discussion of the COM reference counting rules in general, see Inside COM , by Dale Rogerson (Microsoft Press).