previous page
next page

Implementing IUnknown

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:

  1. The object is heap-based because it removes itself using the delete operator. Furthermore, the object's outstanding references completely govern its lifetime. When it has no more references, it deletes itself.

  2. The object is capable of living in a multithread apartment because it manipulates the reference count in a thread-safe manner. Of course, the other methods must be implemented in a thread-safe manner as well for the object to be fully thread safe.

  3. The object is standalone and cannot be aggregated because it does not cache a reference to a controlling outer, nor does it forward the methods of IUnknown to a controlling outer.

  4. The object exposes its interfaces using multiple inheritance.

  5. The existence of the object keeps the server running. The constructor and the destructor are used to lock and unlock the server, respectively.

These common assumptions are not the only possibilities. Common variations include the following:

  1. An object can be global and live for the life of the server. Such objects do not need a reference count because they never delete themselves.

  2. An object might not need to be thread safe because it might be meant to live only in a single-threaded apartment.

  3. An object can choose to allow itself to be aggregated as well as, or instead of, supporting standalone activation.

  4. An object can expose interfaces using other techniques besides multiple inheritance, including nested composition, tear-offs, and aggregation.

  5. You might not want the existence of an object to force the server to keep running. This is common for global objects because their mere existence prohibits the server from unloading.

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.


previous page
next page
Converted from CHM to HTML with chm2web Pro 2.75 (unicode)