Direct3D Programming Tip #3: Use Smart Pointers

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.

9 Responses to “Direct3D Programming Tip #3: Use Smart Pointers”

  1. Brad Says:

    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.

    Like

  2. Sniffy Says:

    Thanks, this is the most programming useful tip I’ve read in awhile. Was not aware of CComPtr.

    Like

  3. Matt Says:

    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.

    Like

  4. Matt Says:

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

    Like

  5. passenger Says:

    that’s cool to read these and not understanding a word
    lol
    no i understood a little

    Like

  6. JK Says:

    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.

    Like

    • legalize Says:

      You can’t use an intrusive pointer on a COM interface. You can use CComPtr, _com_ptr, or boost::shared_ptr.

      Like

      • Leon Says:

        Could you explain why intrusive_ptr can’t be used on a COM interface?

        Like

        • legalize Says:

          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.

          Like


Leave a comment