Direct3D exposes functionality through COM interface pointers. Managing the reference counts on the obtained interfaces is a common problem for new Direct3D programmers. Managing the reference count on an interface becomes trivial if you use a smart pointer wrapper class, such as CComPtr<T>
or boost::shared_ptr<T>
and keep in mind the ownership policies established by the smart pointer class.
In addition to managing reference counts for you, smart pointers can make your code exception safe. When an exception is thrown, if an interface pointer is held within a CComPtr
declared on the stack, then its destructor will be run when the stack is unwound. The destructor for CComPtr
will call IUnknown::Release
on any interface pointer it holds, thus cleaning up any locally obtained resources as the exception unwinds the stack to the nearest exception handler.
CComPtr
CComPtr
is a COM interface smart pointer from the ATL library. Include the file <atlbase.h>
to make CComPtr
accessible to your code. The documentation on MSDN describes the semantics of the class and its methods. Because CComPtr
is part of the ATL library, you will need one of the commercial editions of Visual Studio .NET 2008 in order to obtain the header <atlbase.h>
. However, if you are using Visual C++ Express 2008, you can use the shared_ptr
smart pointer class from boost.
Obtaining Interfaces
Use CComPtr
like this when you get an interface pointer from Direct3D as an out
parameter:
#include <atlbase.h> CComPtr<IDirect3DTexture9> texture; device->CreateTexture(256, 256, 0, D3DUSAGE_WRITEONLY, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &texture, 0);
Storing Interfaces
Use CComPtr
like this when you are declaring a member variable:
class SpecialSurface { public: // ... private: CComPtr<IDirect3DSurface9> m_surface; };
Releasing Interfaces
When you want to release the resource held by the interface pointer, simply assign zero to it:
void Process(IDirect3DDevice9 *device) { CComPtr<IDirect3DTexture9> texture; device->CreateTexture(256, 256, 0, D3DUSAGE_WRITEONLY, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &texture, 0); // ... doing something with the texture here // explicitly release the texture before creating a new one texture = 0; device->CreateTexture(256, 256, 0, D3DUSAGE_WRITEONLY, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &texture, 0); // 2nd created texture is released when texture variable goes out of scope }
When To Use CComPtr
Notice above that we simply pass in the device as its underlying interface pointer. There is no need to use a smart pointer here because nothing in Process changes the lifetime of the device and nothing stores a copy of the interface pointer for later use. Simply use CComPtr
in the placs where you store interface pointers or manage their lifetimes: instance data on classes, global or static variables, local interfaces created for processing in a scope and so-on.
CComPtr/Interface Association
To associate a CComPtr
with an existing interface pointer, use Attach
. To disassociate a CComPtr
with its underlying interface pointer, use Detach
. Attach
should be used with Direct3DCreate9
to avoid a reference count leak:
CComPtr<IDirect3D9> d3d; d3d.Attach(::Direct3DCreate9(D3D_SDK_VERSION));
If we had used an assignment statement:
// Don't do this! The interface is leaked. CComPtr<IDirect3D9> d3d = ::Direct3DCreate9(D3D_SDK_VERSION); // Don't do this! The interface is leaked. CComPtr<IDirect3D9> d3d(::Direct3DCreate9(D3D_SDK_VERSION));
then the reference count would be 2 when this statement completes. That’s because the assignment operator for CComPtr
calls AddRef
. So we would be at a reference count of 1 when Direct3DCreate9
returns and then at 2 when the assignment operator completes. A similar situation would have arisen had we used the constructor directly.
boost::shared_ptr
The shared_ptr
class from boost provides a reference counted pointer that was obtained through new
. When the reference count reaches zero, the destructor calls delete
on the managed pointer. This gives you .NET style reference style semantics for instances of C++ classes.
shared_ptr
can be adapted for a COM interface, so that its destructor calls Release
on the underlying interface pointer. The constructor for shared_ptr
can take a functor that destroys the associated resource when the reference count reaches zero. In this case, the reference count is the one managed by shared_ptr
instead of the underlying COM reference count managed by IUnknown
. This is explained in the boost documentation.
11-January-2009 at 10:01 pm
There is also _com_ptr_t, which ships with every modern version of Visual C++ (including the Express editions) and that does not have a dependency on ATL. It is slightly more verbose in that you have to specify the IID manually, but this is nothing that a bit of macro work can’t solve. I choose to use it over boost::shared_ptr since COM objects already track reference counts internally, so it doesn’t make sense to add yet another reference counting scheme on top of that.
LikeLike
14-January-2009 at 6:40 pm
Thanks, this is the most programming useful tip I’ve read in awhile. Was not aware of CComPtr.
LikeLike
22-January-2009 at 3:44 pm
The other benefit of using _com_ptr_t is that if you #include before you #include any d3d headers then the appropriate typedefs are made for you by the d3d headers for most types in the form IDirect3D9Ptr, IDirect3DDevice9Ptr, etc.
LikeLike
22-January-2009 at 3:45 pm
Oops, the name of the header was stripped out because I used angle brackets. The header you want to #include before any d3d headers is “comdef.h”.
LikeLike
5-March-2009 at 6:03 am
that’s cool to read these and not understanding a word
lol
no i understood a little
LikeLike
3-November-2009 at 2:26 pm
Wouldn’t a boost::intrusive_ptr work better than a shared_ptr? It would be faster since it doesn’t hold an internal reference count and doesn’t take up the space that a shared_ptr does.
LikeLike
7-November-2009 at 11:26 pm
You can’t use an intrusive pointer on a COM interface. You can use CComPtr, _com_ptr, or boost::shared_ptr.
LikeLike
22-August-2011 at 11:21 am
Could you explain why intrusive_ptr can’t be used on a COM interface?
LikeLike
22-August-2011 at 1:17 pm
Looking at the documentation for intrusive_ptr more closely, I was wrong about it not being applicable to a COM interface. I thought intrusive_ptr mandated how the reference count is stored inside the underlying type, but it does not. It manipulates the reference count through functions that you specialize for your objects containing their own reference count. For instance, http://tk11.sourceforge.net/0_2/group__utility__com.html explains how to use it with a COM interface.
So yes, a boost intrusive_ptr could be used with a COM interface and would reuse the existing storage for the reference count. Take a look closely at the above URL; care must be taken when constructing the intrusive pointer that it doesn’t add an additional reference count with the constructor taking two arguments.
LikeLike