A COM object has one responsibility: to implement the methods of IUnknown. Those methods perform two services, lifetime management and runtime type discovery, as follows: interface IUnknown { // runtime type discovery HRESULT QueryInterface([in] REFIID riid, [out, iid_is(riid)] void **ppv); // lifetime management ULONG AddRef(); ULONG Release(); } COM allows every object to implement these methods as it chooses (within certain restrictions, as described in Chapter 5, "COM Servers"). The canonical implementation is as follows: // Server lifetime management extern void ServerLock(); extern void ServerUnlock(); class CPenguin : public IBird, public ISnappyDresser { public: CPenguin() : m_cRef(0) { ServerLock(); } virtual ~CPenguin() { ServerUnlock(); } // IUnknown methods STDMETHODIMP QueryInterface(REFIID riid, void **ppv) { if( riid == IID_IBird || riid == IID_IUnknown ) *ppv = static_cast<IBird*>(this); else if( riid == IID_ISnappyDresser ) *ppv = static_cast<ISnappyDresser*>(this); else *ppv = 0; if( *ppv ) { reinterpret_cast<IUnknown*>(*ppv)->AddRef(); return S_OK; } return E_NOINTERFACE; } ULONG AddRef() { return InterlockedIncrement(&m_cRef); } ULONG Release() { ULONG l = InterlockedDecrement(&m_cRef); if( l == 0 ) delete this; return l; } // IBird and ISnappyDresser methods... private: ULONG m_cRef; }; This implementation of IUnknown is based on several assumptions:
These common assumptions are not the only possibilities. Common variations include the following:
Changing any of these assumptions results in a different implementation of IUnknown, although the rest of the object's implementation is unlikely to change much (with the notable exception of thread safety). These implementation details of IUnknown tend to take a very regular form and can be encapsulated into C++ classes. Frankly, we'd really like to use someone else's tested code and be able to change our minds later without a great deal of effort. We'd also like this boilerplate code to be easily separated from the actual behavior of our objects so that we can focus on our domain-specific implementation. ATL was designed from the ground up to provide just this kind of functionality and flexibility. |