Sunday, February 5, 2012

Using intrusive_ptr with COM

Writing COM code in C++ can be greatly simplified by using a reference-counting smart pointers. Microsoft provides an implementation in the ATL (CComPtr), however I typically don't use ATL in my projects and aren't interested in introducing it as a dependency just for the sake of a single utility class. Boost provides a lightweight reference-counting smart pointer in the form of intrusive_ptr, although it takes a few tweaks to get it to play nice with COM. First, we need to write a couple of functions to tell intrusive_ptr how to interface with COM objects. Since all COM objects are derived from IUnknown, which also defines the reference-counting interface, this is trivial:

inline void intrusive_ptr_add_ref(IUnknown* ptr)
{
    ptr->AddRef();
}

inline void intrusive_ptr_release(IUnknown* ptr)
{
    ptr->Release();
}

With these defined, intrusive_ptr will play nice with COM objects. However, getting them into the intrusive_ptr is a little trickier. The standard technique for COM functions is to take as a parameter a pointer-to-pointer, which, if the operation is successful, will be assigned to the address of the object being created or acquired. Also, incrementing the object's reference count is handled by the callee, not the caller. This behavior is the opposite of what intrusive_ptr expects. Both problems can be solved via a small helper class and function:

template <typename T>
class WrapPtr
{
public:
    WrapPtr(boost::intrusive_ptr<T>& ref)
    : m_ref(ref), m_ptr(0)
    {
    }
   
    ~WrapPtr()
    {
        // The second parameter indicates that the reference count should not be incremented
        m_ref = boost::intrusive_ptr(m_ptr, false);
    }
   
    operator T**()
    {
        return &m_ptr;
    }
   
    operator void**()
    {
        // Some COM functions ask for a pointer to void pointer, such as QueryInterface
        return reinterpret_cast<void**>(&m_ptr);
    }
   
private:
    T* m_ptr;
    boost::intrusive_ptr<T> m_ref;
};

template <typename T>
WrapPtr<T> AttachPtr(boost::intrusive_ptr<T>& ref)
{
    return WrapPtr<T>(ref);
}

The WrapPtr class acts as a proxy for the intrusive_ptr object when calling COM functions. It is designed to be used in the following manner:

D3D11_TEXTURE3D_DESC desc;
boost::intrusive_ptr<ID3D11Texture3D> texture;
HRESULT hr = m_d3dDevice->CreateTexture3D(&desc, NULL, AttachPtr(texture));

Using intrusive_ptr for RAII vastly reduces the verbosity involved in interfacing with COM objects, and makes it much easier to write exception safe code.

No comments:

Post a Comment