IUnknown
COM
is Microsoft's component framework. It was created in the 90's but still used for native development and UWP. All components in this framework must support the IUnknown
interface:
interface IUnknown
{
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef() = 0;
virtual ULONG STDMETHODCALLTYPE Release() = 0;
};
This interface has three functions:
-
QueryInterface
to query and access the components supported
interfaces
AddRef
to increment the reference count
-
Release
to decrement the reference count. When the reference
count goes to zero the component is destroyed.
Client code must adhere to the protocol of incrementing the interface
when using it and releasing the interface when done. Mostly components returned
from functions are already incremented so that client code only need to
decrement the reference count.
Example
For the example here the function to create error info is used. The
address of a pointer must supplied and when successful the interface must be
released:
ICreateErrorInfo* pErrorInfo = nullptr;
HRESULT hr = ::CreateErrorInfo(&pErrorInfo);
if (pErrorInfo)
{
pErrorInfo->Release();
}
One of the major error causes when using COM is that reference counts
are not administered correctly:
-
when reference counts are more released than incremented by client code it
causes cashes and access violations. This may happen beyond the fault
location (in time and space).
-
when too few reference counts are released it may cause resource
leaks. For example on my work in the past a colleague released one
VMR9
interface too little. This resulted in a full thread leak since the VMR9 is a fat object.
Smart pointer
Luckily Microsoft has acknowledged this problem and created smart
pointer classes for COM interfaces. There are two flavor's. One comes from the compiler support classes '_com_ptr_t'. The other one is CComPtr and comes from the
ATL library. The CComPtr is desmontrated in the followign examples.
Code in above example becomes easier especially when there would be multiple return
paths:
CComPtr<ICreateErrorInfo> ptrErrorInfo;
HRESULT hr = ::CreateErrorInfo(&ptrErrorInfo);
if (ptrErrorInfo)
{
//no release necessary
}
As usual with smart pointers they work transparent. One can assign them
to other smart pointers or even return from functions:
CComPtr<ICreateErrorInfo> CreateErrorInfoPtr()
{
CComPtr<ICreateErrorInfo> ptrErrorInfo;
HRESULT hr = ::CreateErrorInfo(&ptrErrorInfo);
return ptrErrorInfo;
}
const CComPtr<ICreateErrorInfo> ptr = CreateMyErrorInfoPtr();
With returning raw pointers a leak is easily created when the client
code ignores the created interface return value. In above case with smart
pointers it is suboptimal but it wouldn't hurt when client code ignores the return value since
the returned temporary object goes out of scope and releasing thereby the
created interface.
Implementation
A possible implementation could be as follow (borrowed and modified from the original source):
template <class T>
class CComPtr
{
public:
CComPtr()
: m_p(nullptr)
{
}
CComPtr(T* p)
: m_p(p)
{
if (m_p != nullptr)
m_p->AddRef();
}
CComPtr(const CComPtr& rptr)
: CComPtr(rptr.m_p)
{
}
~CComPtr()
{
if (m_p)
m_p->Release();
}
CComPtr& operator=(const CComPtr& rptr)
{
if (m_p != rptr.m_p)
{
if (m_p)
m_p->Release();
m_p = rptr.m_p;
m_p->AddRef();
}
return *this;
}
private:
T* m_p;
};
Conclusion
CComPtr is one the best addition to COM programming. It greatly
simplifies COM client implementaitons and solves almost completely all
reference counting and tracking issues. It's actually hard to do wrong with
CComPtr since it also asserts when a contained interface is overwritten
accidently.
'Effective COM' mentions in item 22 that 'smart interface pointers add
at least as much complexity as they remove'. I strongly disagree as can be
read from this article. The book mentions a small problem with old CComPtr
which is also solved in the latest release of ATL.