The Rules of the Component Object Model

Charlie Kindel
Program Manager, Windows NT

October 20, 1995

Abstract

This paper is intended to be a quick reference for the primary rules of using and implementing Microsoft® Component Object Model (COM) objects. Readers interested in gaining a better understanding of what COM is, as well as the motivations behind its design and philosophy, should read the first two chapters of the Component Object Model Specification (MSDN Library, Specifications). Chapter 1 is a brief introduction, and Chapter 2 provides a thorough overview. The information presented here is all taken from the COM specification.

Rule #1: Must Implement IUnknown

An object is not a Microsoft® Component Object Model (COM) object unless it implements at least one interface that at minimum is IUnknown.

Interface Design Rules

Implementing IUnknown

Symmetric: pA->QueryInterface(IID_IA, ...) must succeed (a>>a)
Reflexive: If, in line 4, pB was successfully obtained, then
  pB->QueryInterface(IID_IA, ...)

must succeed (a>>b, then b>>a).

Transitive: If, in line 4, pB was successfully obtained, and we do
  IC * pC = NULL;
  hr = pB->QueryInterface(IID_IC, &pC);    //Line 7

and pC is successfully obtained in line 7, then

  pA->QueryInterface(IID_IC, ...)

must succeed (a>>b, and b>>c, then a>>c).


Memory Management Rules

Reference-Counting Rules

Rule 1: AddRef must be called for every new copy of an interface pointer, and Release called for every destruction of an interface pointer, except where subsequent rules explicitly permit otherwise.

The following rules call out common nonexceptions to Rule 1.

Rule 2: Special knowledge on the part of a piece of code of the relationships of the beginnings and the endings of the lifetimes of two or more copies of an interface pointer can allow AddRef/Release pairs to be omitted.

See the excellent "Managing Object Lifetimes in OLE" technical article by Douglas Hodges, and Chapter 3 of Inside OLE, 2nd edition, by Kraig Brockschmidt (MSDN Library, Books) for more information on reference-counting.

COM Application Responsibilities

Each process that uses COM in any way—client, server, object implementor—is responsible for three things:

  1. Verify that the COM Library is a compatible version with the COM function CoBuildVersion.

  2. Initialize the COM Library before using any other functions in it by calling CoInitialize.

  3. Uninitialize the COM Library when it is no longer in use by CoUninitialize.

In-process servers can assume that the process they are being loaded into has already performed these steps.

Server Rules

Creating Aggregatable Objects

Creating objects that can be aggregated is optional; however, it is simple to do, and doing so has significant benefits. The following rules must be followed in order to create an object that is aggregatable (often called the inner object).

The code fragment below illustrates a correct implementation of an aggregatable object using the nested class approach to implementing interfaces:

// CSomeObject is an aggregatable object that implements
// IUnknown and ISomeInterface
class CSomeObject : public IUnknown
{
    private:
        DWORD         m_cRef;            // Object reference count
        IUnknown*    m_pUnkOuter;        // Outer unknown, no AddRef

        // Nested class to implement the ISomeInterface interface
        class CImpSomeInterface : public ISomeInterface
        {
            friend class CSomeObject ;
            private:
                DWORD           m_cRef;    // Interface ref-count, for debugging
                IUnknown*    m_pUnkOuter;  // Outer unknown, for delegation
            public:
                CImpSomeInterface() { m_cRef = 0;    };
                ~ CImpSomeInterface(void) {};

                // IUnknown members delegate to the outer unknown

                // IUnknown members do not control lifetime of object

                STDMETHODIMP     QueryInterface(REFIID riid, void** ppv)
                {    return m_pUnkOuter->QueryInterface(riid,ppv);  };

                STDMETHODIMP_(DWORD) AddRef(void)
                {     return m_pUnkOuter->AddRef();   };

                STDMETHODIMP_(DWORD) Release(void)
                {     return m_pUnkOuter->Release();   };

                // ISomeInterface members
                STDMETHODIMP SomeMethod(void) 
                {    return S_OK;    };
        } ;
        CImpSomeInterface m_ImpSomeInterface ;
    public:
        CSomeObject(IUnknown * pUnkOuter)
        {
            m_cRef=0;

            // No AddRef necessary if non-NULL as we're aggregated.

            m_pUnkOuter=pUnkOuter;
            m_ImpSomeInterface.m_pUnkOuter=pUnkOuter;    
        } ;
        ~CSomeObject(void) {} ;

        // Static member function for creating new instances (don't use

        // new directly). Protects against outer objects asking for interfaces 

        // other than IUnknown

        static HRESULT Create(IUnknown* pUnkOuter, REFIID riid, void **ppv)
        {
            CSomeObject*        pObj;
            if (pUnkOuter != NULL && riid != IID_IUnknown)
                return CLASS_E_NOAGGREGATION;
            pObj = new CSomeObject(pUnkOuter);
            if (pObj == NULL)
                return E_OUTOFMEMORY;
            // Set up the right unknown for delegation (the non-aggregation 
                                                        case)
            if (pUnkOuter == NULL)
                pObj->m_pUnkOuter = (IUnknown*)pObj ;
            HRESULT hr;
            if (FAILED(hr = pObj->QueryInterface(riid, (void**)ppv)))
                delete pObj ;
            return hr;
        }

        // Implicit IUnknown members, non-delegating

        // Implicit QueryInterface only controls inner object

        STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
        {
            *ppv=NULL;
            if (riid == IID_IUnknown)   
                *ppv=this;
            if (riid == IID_ISomeInterface)  
                *ppv=&m_ImpSomeInterface;
            if (NULL==*ppv)  
                return ResultFromScode(E_NOINTERFACE);
            ((IUnknown*)*ppv)->AddRef();
            return NOERROR;
        } ;
        STDMETHODIMP_(DWORD) AddRef(void)
        {      return ++m_cRef; };
        STDMETHODIMP_(DWORD) Release(void)
        {    
            if (--m_cRef != 0)
                return m_cRef;
            delete this;
            return 0;
        };
};

Aggregating Objects

When developing an object that aggregates in another object, these rules must be followed:

Apartment Threading Model

The details of apartment-model threading are actually quite simple, but must be followed carefully, as follows:

Every object lives on a single thread (within a single apartment).