MFC/COM Objects 8: Revisiting Multiple Inheritance Without MFC

Dale Rogerson
Microsoft Developer Network Technology Group

September 15, 1995

Click to open or copy the files in the MI_Appliances sample application for this technical article.

Click to open or copy the files in the HOUSE4 sample application for this technical article.

Abstract

This article is a follow-up to Nigel Thompson's article "MFC/COM Objects 5: Using Multiple Inheritance" in the Microsoft Developer Network (MSDN) Library. This article discusses how to implement 32-bit Component Object Model (COM) objects using multiple inheritance instead of class nesting, and without using Microsoft Foundation Class Library (MFC) OLE support. Accompanying this article is MI_Appliances.dll, which contains several COM objects for use with the HOUSE4 sample application (originally provided with the article "MFC/COM Objects 4: Aggregation").

Note   Before running the sample, you must register the location of MI_Appliances.dll in the registry. See the first article in this series ("MFC/COM Objects 1: Creating a Simple Object") for details on how to do this.

Introduction

In his article "MFC/COM Objects 5: Using Multiple Inheritance," Nigel Thompson explained how to implement Component Object Model (COM) objects using multiple inheritance instead of the nested class approach provided by the Microsoft® Foundation Class Library (MFC). Nigel wanted to use MFC as much as possible, but multiple inheritance and MFC don't mix very well, so he decided not to use multiple inheritance.

However, after implementing several COM objects using MFC, Nigel realized that using multiple inheritance had to be better, so he put me on a mission to research the implementation of COM objects with multiple inheritance, without using MFC.

When using MFC, a simple COM interface function looks like this:

STDMETHODIMP CTelevision::XDrawing::GetRect(CRect* pRect)
{

    METHOD_PROLOGUE(CTelevision, Drawing);

    if (!pRect) return E_INVALIDARG;
    pThis->m_dibImage.GetRect(pRect);
    return NOERROR;
}

The code shown in boldface above is required when implementing COM objects with embedded interfaces, but is not required when multiple inheritance is used. The same COM object implemented with multiple inheritance looks like this:

STDMETHODIMP CTelevision::GetRect(CRect* pRect)
{
   if (!pRect) return E_INVALIDARG;
   m_dibImage.GetRect(pRect);
   return NOERROR;
}

With the multiple inheritance solution, you don't need the METHOD_PROLOGUE macro or the crazy pThis pointer. Building COM objects with MFC feels like programming with C instead of C++.

Now, OLE Automation is a different story. MFC supports OLE Automation very well, similar to the way it supports menu commands. If you are writing an OLE Automation server instead of a COM object, MFC is the way to go.

In this article, we will discuss the implementation of COM objects using multiple inheritance. Specifically, I will:

The Sample Code

To demonstrate multiple inheritance, I rewrote all of the COM objects in Nigel's Appliances.dll to use multiple inheritance. The new dynamic-link library (DLL), MI_Appliances.dll, completely replaces Nigel's Appliances.dll.

The only class I changed was CNotifyListObject. In Appliances.dll, a non-CObject-derived pointer was incorrectly being stuffed into a CObList. It's amazing what the compiler will let you get away with when you cast something, and it's even more amazing that this worked. I replaced the CObList with:

CTypedPtrList<CPtrList, CUserInfo*> m_NotifyList;

I didn't want to make any changes to the House sample, so the COM objects in MI_Appliances use the same GUIDs as the corresponding objects in Appliances.dll. To use MI_Appliances.dll, change the paths in MI_Appliances.reg to point to MI_Appliances.dll on your system, then double-click MI_Appliances.reg to add these entries to the registry.

The HOUSE4 sample can work with a mixture of objects from Appliances.dll and MI_Appliances.dll. You can edit the registry to specify which appliance comes from which DLL.

To use MI_Appliances.dll, you must first install Nigel's HOUSE4 sample application, then install MI_Appliances in the HOUSE4 directory. MI_Appliances uses the common header files in the INCLUDE directory.

Architecture of a Multiple-Inheritance COM Object

Let's say that we wanted a COM object to support the IDrawing and IOutlet interfaces using multiple inheritance:

class IOutlet : public IUnknown {...} ;
class IDrawing : public IUnknown {...} ;

class CLightBulb : public IDrawing,
                   public IOutlet
{
public:
   // IUnknown implementation
   virtual HRESULT __stdcall QueryInterface(REFIID riid, void **ppv) ;
   virtual ULONG   __stdcall AddRef() ;
   virtual ULONG   __stdcall Release() ;

   // IDrawing interface implementation
   virtual HRESULT __stdcall Draw(CDC* pDC, int x, int y);
   virtual HRESULT __stdcall SetPalette(CPalette* pPal);
   virtual HRESULT __stdcall GetRect(CRect* pRect);

   // IOutlet interface implementation
   virtual HRESULT __stdcall On();
   virtual HRESULT __stdcall Off();
   virtual HRESULT __stdcall GetState(BOOL* pState);
.
.
.
   // Implementation details here.
.
.
.
} ;

Notice that we need to define IUnknown only once, although we inherit IUnknown through both IDrawing and IOutlet. If you find this surprising, remember that C::f overrides both A::f and B::f in the following example:

class A 
{
   virtual int f();
} ;

class B 
{
   virtual int f();
} ;

class C : public A, public B 
{
   virtual int f() ; 
} ;

Because all interfaces require an IUnknown implementation, reusing a single implementation of IUnknown would be beneficial. Therefore, we define a class called CUnknown to implement IUnknown.

class CUnknown : public IUnknown {...} ;

class CLightBulb : public CUnknown,
                   public IDrawing,
                   public IOutlet
{
} ;

However, this doesn't work. CUnknown cannot implement the IUnknown interfaces inherited through IDrawing and IOutlet, because pure virtual functions must be implemented in a derived class. CUnknown can only implement the IUnknown that it inherits. C++ does not allow a base class to implement methods in another base class, so we have to define IUnknown in CLightBulb.

Figure 1 shows the inheritance graph for CLightBulb, as defined above. Abstract classes are shown in square boxes.

Figure 1. Inheritance graph for IUnknown implemented in CUnknown

Hope is not completely lost. We could implement two IUnknown interfaces: one in CUnknown and another in CLightBulb. The IUnknown in CUnknown would do the real work, and the IUnknown in CLightBulb would delegate to the real IUnknown. The IUnknown in CUnknown is called the delegating IUnknown, while CUnknown contains the non-delegating IUnknown. Therefore, let's call the IUnknown interface that does the real work, INonDelegatingUnknown:

DECLARE_INTERFACE(INonDelegatingUnknown)
{
  STDMETHOD(NonDelegatingQueryInterface)(THIS_ REFIID, LPVOID *) PURE;
  STDMETHOD_(ULONG, NonDelegatingAddRef)(THIS) PURE;
  STDMETHOD_(ULONG, NonDelegatingRelease)(THIS) PURE;
};

class CUnknown : public INonDelegatingUnknown {...} ;

For convienience, we can create a macro, DECLARE_IUNKNOWN, that delegates to the IUnknown defined in CUnknown. Figure 2 shows the class hierarchy.

Figure 2. Class hierarchy using delegating and non-delegating IUnknowns

Because our object supports more than just the IUnknown interface, we need to override NonDelegatingQueryInterface to handle the other interfaces. Thus, the header file for our CLightBulb class would look like this:

class CLightBulb : public CUnknown,
                   public IDrawing,
                   public IOutlet
{
public:
   // Delegating IUnknown

   DECLARE_IUNKNOWN ;


   // IUnknown implementation

   virtual HRESULT __stdcall 

           NonDelegatingQueryInterface(REFIID riid, void **ppv) ;


   // IDrawing interface implementation
   virtual HRESULT __stdcall Draw(CDC* pDC, int x, int y);
   virtual HRESULT __stdcall SetPalette(CPalette* pPal);
   virtual HRESULT __stdcall GetRect(CRect* pRect);

   // IOutlet interface implementation
   virtual HRESULT __stdcall On();
   virtual HRESULT __stdcall Off();
   virtual HRESULT __stdcall GetState(BOOL* pState);
.
.
.
   // Implementation details here
.
.
.
} ;

This discussion covers the basics of implementing a COM object using multiple inheritance. I have skipped the entire subject of class factories and creating COM objects because these topics have nothing to do with multiple inheritance. See Inside OLE 2 by Kraig Brockschmidt (MSDN Library, Books) for more information on creating COM objects.

The MI_Appliance sample application contains two files: COMBASE.H and COMBASE.CPP, which contain the implementation of CUnknown and CClassFactory.

The astute reader will realize that I haven't shown the implementation of the DECLARE_IUNKNOWN macro. This is intentional. First, let's talk about the second reason for having a delegating IUnknown and a non-delegating IUnknown. That reason is aggregation.

Delegating vs. Non-Delegating IUnknown

The best way to understand why aggregation requires a delegating and a non-delegating IUnknown is to look at the possible ways that QueryInterface, AddRef, and Release are called in the aggregated and non-aggregated cases, and assume that we only have a single IUnknown interface. If the object does not support aggregation, the non-delegating IUnknown is used. When the object is aggregated, the delegating IUnknown is used.

For this discussion, let's assume we have two objects, Boss and Peon. The Boss object is controlling the Peon object. This relationship is represented as a standard OLE object diagram in Figure 3.

Figure 3. Boss controls Peon

When Boss creates Peon, it calls CoCreateInstance:

::CoCreateInstance(CLSID_Peon,
                   pUnkOwner,
                   CLSCTX_INPROC_SERVER,
                   IID_IUnknown,
                   (void**)&pUnkPeon);

CoCreateInstance returns a pointer to the Peon's IUnknown. If Peon had two IUnknown interfaces, its ClassFactory would return the pointer to the primary (or non-delegating) IUnknown. However, we are attempting to implement Peon with only one IUnknown, so its ClassFactory returns a pointer to the one and only IUnknown interface. Peon should also increment its reference count before returning the IUnknown pointer. All of this can be implemented without using QueryInterface or AddRef, since Peon can implement its own ClassFactory object with its own CreateInstance method.

If Boss wants to get the IPeon interface, it queries Peon's IUnknown interface:

pUnkPeon->QueryInterface(IID_IPeon, (void**)pIPeon) ;

This call returns a pointer to the IPeon interface in Peon and increments the reference counter on Boss.

If pIPeon is queried for IUnknown, the COM specification requires QueryInterface to return the IUnknown for Boss instead of the IUnknown for Peon:

pIPeon->QueryInterface(IID_IUnknown, (void**)pUnk) ;
ASSERT(pUnk == pUnkBoss) ;
ASSERT(pUnk != pUnkPeon) ;

This QueryInterface call also increments the reference count on Boss.

Here's a single QueryInterface that does all of this work:

HRESULT CPeon::QueryInterface(REFIID riid, void** ppv)
{
   if (IID_IUnknown == riid)
   {
      if (m_pUnkOwner)
      {
         // Aggregated
         *ppv = m_pUnkOwner ;      // Return IUnknown for Boss.
      }
      else
      {
         // Non-aggregated
         *ppv = (IUnknown*)this ; // Return IUnknown for Peon.
      }
   }
   else if (IID_IPeon == riid)
   {
      *ppv = (IPeon*)this ;       // Return IPeon pointer.
   }
   else
   {
      *ppv = NULL ;
      return E_NOINTERFACE ;
   }   

   // AddRef interface
   if (m_pUnkOwner)
   {
      // Aggregated   
      m_pUnkOwner->AddRef() ;  // AddRef Boss
   }
   else
   {
     // Non-aggregated
     AddRef() ;              // AddRef Peon
   }
   return NOERROR ;
}

Notice that QueryInterface cannot be called from ClassFactory::CreateInstance to get the IUnknown for Peon, unless m_pUnkOwner is set after the call. Otherwise, the reference count for Boss will be incrememented instead of the reference count for Peon.

So, why have two IUnknown interfaces if only one QueryInterface function is needed? Because it's not possible to implement AddRef and Release functions that work for both the aggregated and non-aggregated cases.

Using the variables defined above:

pUnkPeon->AddRef() ;

should increment the reference count on Peon, while:

pIPeon->AddRef() ;

should increment the reference count on Boss. CPeon::Release works in the same manner. A first attempt at coding AddRef might be:

CPeon::AddRef()
{
   if (m_pUnkOwner)
      m_pUnkOwner->AddRef() ;
   else
      AddRef() ;
}

pUnkPeon and pIPoen both point to the same object in which pUnkOwner is non-NULL, so this solution will not work. Somehow, pUnkPeon must invoke a different AddRef method than that invoked by pIPeon. Because both pUnkPeon and pIPeon are pointers to interfaces, the most straightforward solution is to have them point to two different interfaces: One interface will act as the IUnknown for Peon, and the other interface will delegate calls to Boss.

Using Two IUnknowns

To support aggregation with two IUnknown interfaces, CUnknown contains an IUnknown pointer, m_pUnkOwner, which points to the owner of the object. The non-delegating IUnknown is the owner of the object if it is not aggregated. m_pUnkOwner is set in the constructor:

CUnknown::CUnknown(IUnknown* pUnk, HRESULT* phr)
{
   InterlockedIncrement(&m_ObjectCount) ;

   if (pUnk == NULL)
      m_pUnkOwner = (IUnknown*)(INonDelegatingUnknown*)this ;   
   else
      m_pUnkOwner = pUnk ;

   m_RefCount = 0 ;
}

The DECLARE_IUNKNOWN macro implements IUnknown by delegating to the IUnknown that m_pUnkOwner points to. Here's the definition of DECLARE_IUNKNOWN:

#define DECLARE_IUNKNOWN                                        \
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv) {      \
        return GetOwner()->QueryInterface(riid,ppv);            \
    };                                                          \
    STDMETHODIMP_(ULONG) AddRef() {                             \
        return GetOwner()->AddRef();                            \
    };                                                          \
    STDMETHODIMP_(ULONG) Release() {                            \
        return GetOwner()->Release();                           \
    };

where GetOwner is defined as follows:

IUnknown* CUnknown::GetOwner() const
{ return m_pUnkOwner; }

In CUnknown's constructor, note that m_pUnkOwner is initialized with the line:

m_pUnkOwner = (IUnknown*)(INonDelegatingUnknown*)this ;

in the non-aggregated case. In the non-aggregated case, any call to the delegating IUnknown results in calls to the non-delegating IUnknown. More precisely:

IUnknown* pUnk = this;
pUnk->AddRef() ;

will call the delegating AddRef, while:

IUnknown* pUnk = (IUnknown*)(INonDelegatingUnknown*)this;
pUnk->AddRef

will call the NonDelegatingAddRef.

Now that we know the architecture of a COM object built using multiple inheritance, it's time to see how we can actually implement the object.

Implementing a COM Object Using Multiple Inheritance

One big drawback to implementing COM objects using multiple inheritance is that you can't use ClassWizard, and AppWizard will help only by building a DLL for you. However, once you have implemented a COM object, implementing other objects is a simple process of cutting and pasting code. In this section, we will explain how to implement a COM object using multiple inheritance and the code I've provided in COMBASE.H and COMBASE.CPP. The implementation consists of two steps: First you create a DLL to hold the COM object, then you write the code for the COM object.

Creating the DLL

A COM object resides either in a .DLL or in an .EXE. In this example, we'll place the COM object in a DLL. This process consists of four steps:

  1. Create an MFC DLL using AppWizard.

  2. Remove MFC OLE Automation code.

  3. Add the CFactoryTemplate array.

  4. Add COMBASE.CPP to the project.

Step 1. Create an AppWizard DLL

Use AppWizard to create a DLL that statically links to the MFC library. COM objects cannot use the shared version of the MFC library. Also, select the "OLE Automation" check box. We will replace all of the MFC OLE Automation code with our own code, but it's nice to have AppWizard put in stubs for the functions.

Step 2. Remove MFC OLE Automation code

In this step, we'll replace the MFC OLE code with the code I've provided in COMBASE.H and COMBASE.CPP. Comment out the call to COleObjectFactory::RegisterAll in your DLL's InitInstance. Also comment out the DllRegisterServer function, and remove its entry from the .DEF file. Change the DllGetClassObject and DllCanUnloadNow functions to match the listings below:

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
   // return AfxDllGetClassObject(rclsid, riid, ppv);
   return CClassFactory::GetClassObject(rclsid, riid, ppv);
}

STDAPI DllCanUnloadNow(void)
{
   // return AfxDllCanUnloadNow();
   return CClassFactory::CanUnloadNow();
}

CClassFactory::GetClassObject creates the rclsid COM object returning the riid interface in ppv.

Step 3. Add the CFactoryTemplate array

In your DLL's main file, define the CFactoryTemplate array:

CFactoryTemplate g_Templates[] =
{
   // Fill with CLSID and CreateInstance function.

} ;
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]) ;

This array is used by CClassFactory to create the COM objects supported by the DLL. The array will be filled later when we actually add a COM object. CClassFactory looks into the array for the CLSID passed to GetClassObject. After finding the CLSID, CClassFactory calls the creation function listed in the CFactoryTemplate structure.

Step 4. Add COMBASE.CPP to the project

Add the COMBASE.CPP and COMBASE.H files (which are part of the MI_Appliances sample application) to the project. These files implement CUnknown, CClassFactory, and CFactoryTemplate.

Creating the COM Object

Now that we have a DLL to hold the COM objects, we can create a COM object following these steps:

  1. Create the C++ class.

  2. Implement CreateInstance.

  3. Add CreateInstance to the CFactoryTemplate array.

  4. Implement the constructor.

  5. Implement NonDelegatingQueryInterface.

  6. Add DECLARE_IUNKNOWN.

  7. Put initialization code in OnInit.

  8. Implement the interfaces.

  9. Create the GUIDs.

  10. Update the registry.

Step 1. Create the C++ class

Create a header file and implementation file for the C++ class that implements the COM object. You have to do this on your own; ClassWizard will not help.

The C++ class should inherit from CUnknown and from any other interfaces that it supports. For example, CLightBulb is defined as follows:

class CLightBulb : public CUnknown,
                   public IDrawing,
                   public IOutlet
{ ... } ;

Step 2. Implement CreateInstance

CClassFactory calls CreateInstance to create the COM object. CreateInstance is a static member function of our object class. All CreateInstance functions are basically the same and can be built by copying the code from an existing implementation.

CUnknown* CLightBulb::CreateInstance(IUnknown* pUnk, HRESULT* phr)
{
   CLightBulb* pNewObject = new CLightBulb(pUnk, phr) ;
   if (pNewObject == NULL)
      *phr = E_OUTOFMEMORY ;

   return pNewObject ;
}

It is possible to implement CreateInstance using C++ templates; however, I felt that this would make the source code unnecessarily confusing. If there is enough interest, I can present a version of CFactoryTemplate that uses C++ templates in a future article.

Step 3. Add the CreateInstance function to the CFactoryTemplate array

Add the CreateInstance function for your object to the CFactoryTemplate array so that CClassFactory can create the object.

CFactoryTemplate g_Templates[] =
{
   {&CLSID_LightBulb, CLightBulb::CreateInstance}
}

You can put as many objects as you want in this array. CClassFactory supports the creation of any object with a CreateInstance function.

Step 4. Implement the constructor

Implement the constructor for the C++ class. Make sure that you initialize the CUnknown base class. The constructor must take an IUnknown pointer and an HRESULT pointer to pass to the CUnknown base class.

CLightBulb::CLightBulb(IUnknown* pUnk, HRESULT* phr)
   : CUnknown(pUnk, phr) 
{
   m_pPal = NULL;
   m_bIsOn = FALSE;
   m_dibImageOn.Load(IDR_DIB_LIGHTBULB_ON);
   m_dibImageOff.Load(IDR_DIB_LIGHTBULB_OFF);
}

I like to implement protected constructors, because the COM objects should be constructed only by the CreateInstance function or by a derived class.

Step 5. Implement NonDelegatingQueryInterface

In NonDelegatingQueryInterface, add support for the interfaces that your object handles. CLightBulb supports the IOutlet and IDrawing interfaces:

HRESULT CLightBulb::NonDelegatingQueryInterface(REFIID riid, 
                                                void** ppv)
{
   if (riid == IID_IOutlet)
   {
      return GetInterface((IOutlet*)this, ppv) ;
   }
   else if (riid == IID_IDrawing)
   {
      return GetInterface((IDrawing*)this, ppv) ;
   }
   else
      return CUnknown::NonDelegatingQueryInterface(riid, ppv) ;
}

Pass any interface IDs that you don't handle to CUnknown::NonDelegatingQueryInterface.

CUnknown::GetInterface is a helper function in COMBASE.CPP that sets ppv with the pointer to the desired interface and calls AddRef on that interface. It is implemented as follows:

HRESULT CUnknown::GetInterface(IUnknown* pUnk, void** ppv) 
{
   *ppv = pUnk ;
   ((IUnknown*)pUnk)->AddRef() ;
   return NOERROR ;
}

If a COM object supports interfaces through aggregation, its NonDelegatingQueryInterface should call QueryInterface on the aggregated object. The Radio object in MI_Appliances.dll implements the IBitmap and IDrawing interfaces by aggregating a BitmapObject. CRadio::NonDelegatingQueryInterface forwards all requests for IBitmap and IDrawing to the BitmapObject.

HRESULT __stdcall CRadio::NonDelegatingQueryInterface(REFIID riid, void** ppv)
{
   if (riid == IID_IApplianceUI)
   {
      return GetInterface((IApplianceUI*)this, ppv) ;
   }
   else if ((riid == IID_IBitmap) || (riid == IID_IDrawing))
   {
      return m_punkBitmap->QueryInterface(riid, ppv);
   }
   else
   {
      return CUnknown::NonDelegatingQueryInterface(riid, ppv) ;
   }
}

Step 6. Add DECLARE_IUNKNOWN

Add DECLARE_IUNKNOWN to your header file to declare the delegating IUnknown used in aggregation. This is required even if you aren't using aggregation.

Step 7. Put Initialization code in OnInit

If you have additional initialization code, override CUnknown::OnInit. This member function is mainly used for aggregating with other COM objects. (See the "Aggregation" section later in this article.)

Step 8. Implement the interfaces

Implement the COM interfaces supported by your COM object. The IOutlet interface is implemented as follows for CLightBulb:

HRESULT CLightBulb::On()
{
    m_bIsOn = TRUE;
    return NOERROR;
}

HRESULT CLightBulb::Off()
{
    m_bIsOn = FALSE;
    return NOERROR;
}

HRESULT CLightBulb::GetState(BOOL* pState)
{
    if (!pState) return E_INVALIDARG;
    *pState = m_bIsOn;
    return NOERROR;
}

Step 9. Create the GUIDs

All COM objects need GUIDs for identification. Refer to Nigel Thompson's article "MFC/COM Objects 1: Creating a Simple Object" for more information.

Step 10. Update the registry

Make sure that you add the CLSIDs of your objects to the registry. You will need to make a .reg file for your COM objects. Refer to Nigel Thompson's article "MFC/COM Objects 1: Creating a Simple Object" for more information.

The Finished Product

Here is the finished header file for CLightBulb:

#ifndef __CLightBulb_H__
#define __CLightBulb_H__

class CLightBulb : public CUnknown,
                   public IDrawing,
                   public IOutlet
{
public:
   // Delegating IUnknown
   DECLARE_IUNKNOWN ; 

   // Called by ClassFactory
   static CUnknown* CreateInstance(IUnknown* pUnk, HRESULT* phr) ;

   // Overload to support IDrawing and IOutlet
   virtual HRESULT __stdcall NonDelegatingQueryInterface(REFIID riid,
                                                         void** ppv) ;

   // IDrawing interface implementation
   virtual HRESULT __stdcall Draw(CDC* pDC, int x, int y);
   virtual HRESULT __stdcall SetPalette(CPalette* pPal);
   virtual HRESULT __stdcall GetRect(CRect* pRect);

   // IOutlet interface implementation
   virtual HRESULT __stdcall On();
   virtual HRESULT __stdcall Off();
   virtual HRESULT __stdcall GetState(BOOL* pState);

protected:
   // Constructor
   CLightBulb(IUnknown* pUnk, HRESULT* phr) ;

   // Destructor
   virtual ~CLightBulb() ;

protected:
   // Member variables
   CDIB m_dibImageOn;
   CDIB m_dibImageOff;
   CPalette* m_pPal;
   BOOL m_bIsOn;
} ;

You can use this code as a guide for implementing your own COM objects that use multiple inheritance.

Path of Execution

Now that we have built CLightBulb, let's trace the path of execution as the House sample application creates CLightBulb. If you aren't interested in following the code as it executes, you can skip this section. If you are interested, it would be helpful to follow along in the sample code accompanying this article.

The House sample executes the following code:

HRESULT hr = ::CoCreateInstance(CLSID_LightBulb,
                                NULL, 
                                CLSCTX_INPROC_SERVER,
                                IID_IUnknown,
                                (LPVOID*)ppIUnknown);

::CoCreateInstance calls DLLGetClassObject in MI_Appliances.CPP. DLLGetClassObject calls CClassFactory::GetClassObject, which looks in the CFactoryTemplates in the g_Templates array for CLSID_LightBulb. When it finds CLSID_LightBulb, it creates a CClassFactory object using the CFactoryTemplate corresponding to CLSID_LightBulb.

::CoCreateInstance then calls CClassFactory::CreateInstance, which calls CFactoryTemplate::CreateInstance. This function calls CLightBulb::CreateInstance via a function pointer, which is initialized in the g_Templates array. CLightBulb::CreateInstance finally creates a CLightBulb object using the new operator.

Boy, that was a lot of work to create a CLightBulb object. The process basically translate a CLSID_LightBulb into:

CLightBulb* pLightBulb = new CLightBulb(pUnk, phr) ;

This setup is complex, because CClassFactory is not hardwired to create a single COM object. CClassFactory can create any class that has a CreateInstance function and a CFactoryTemplate object that has been filled in correctly.

Well, CClassFactory::CreateInstance still has work to do before it can return execution to the House sample. Here is the code for CClassFactory::CreateInstance:

STDMETHODIMP CClassFactory::CreateInstance(IUnknown* pUnkOuter, 
                                           REFIID riid, 
                                           void** ppv)
{
   if ((pUnkOuter != NULL) &&  
      (riid != IID_IUnknown))
   {
     // Aggregated objects can only query IUnknown.
     return ResultFromScode(CLASS_E_NOAGGREGATION);
   }

   HRESULT hr = NOERROR ;
   CUnknown *pObj = m_pTemplate->CreateInstance(pUnkOuter, &hr);
   if (pObj == NULL) return hr;
   if (FAILED(hr))
   {
      delete pObj;
      return hr;
   }

   if (!pObj->Init())
   {
      delete pObj ;
      return E_OUTOFMEMORY ;
   }

   hr = pObj->NonDelegatingQueryInterface(riid, ppv);
   if (FAILED(hr))
   {
      delete pObj;
      return hr;
   }

   ASSERT(*ppv);
   return hr;
}

CClassFactory::CreateInstance next calls the object's CUnknown::Init member function, which is discussed in the next section.

Finally, CClassFactory::CreateInstance calls CLightBulb::NonDelegatingQueryInterface, which returns a pointer to the interface specified by riid and increments the reference count for this instance of a CLightBulb object. Because House requests the IUnknown interface, CLightBulb::NonDelegatingQueryInterface passes control to CUnknown::NonDelegatingQueryInterface, which returns the following pointer for IUnknown:

(IUnknown*)(INonDelegatingUnknown*)this

The non-delegating IUnknown is returned whenever an object is created. See the section "Delegating vs. Non-Delegating IUnknown" earlier in this article for more information.

Now, CClassFactory::CreateInstance returns control back to the House sample, which executes the following line:

(*ppIUnknown)->QueryInterface(IID_IDrawing, (LPVOID*)&pIDrawing) ;

querying the LightBulb object for its IDrawing interface. This results in a call to CLightBulb::NonDelegatingQueryInterface, which returns a pointer to CLightBulb's IDrawing interface and increments its reference count.

Here is the control flow (I've presented it like a call stack dump):

CMainFrame::OnCreate [House]
  CMainFrame::CreateAppliance [House]
    ::CoCreateInstance [OLE]
      DLLGetClassObject
        CClassFactory::GetClassObject
          CClassFactory::CClassFactory
      CClassFactory::CreateInstance
        CFactoryTemplate::CreateInstance
          CLightBulb::CreateInstance
            CLightBulb::CLightBulb
               CUnknown::CUnknown
        CLightBulb::Init
          CLightBulb::OnInit
        CLightBulb::NonDelegatingQueryInterface
          CUnknown::NonDelegatingQueryInterface
            CUnknown::GetInterface
              CUnknown::NonDelegatingAddRef
    [House calls QueryInteface for IDrawing]
    CLightBulb::NonDelegatingQueryInteface
      CUnknown::GetInterface
        CLightBulb::AddRef
          CUnknown::NonDelegatingAddRef     

Wow, that's a lot of code!

Aggregation

The previous section traced the program flow for the creation of the LightBulb COM object. Now let's look at the program flow when a COM object aggregates itself with another COM object.

CStandardLamp uses aggregation to implement its INotifySrc interface. CClassFactory::CreateInstance calls CUnknown::Init:

BOOL CUnknown::Init()
{
   m_RefCount++ ;
   BOOL bResult = OnInit() ;
   m_RefCount-- ;
   return bResult ;
}

CUnknown::Init calls the CUnknown::OnInit virtual function, which is CStandardLamp overrides. The reference count is artificially incremented before OnInit, because it is zero at this time, and aggregating an object in OnInit could result in the CStandardLamp instance getting deleted.

CStandardLamp::OnInit is listed below with the error checking removed.

BOOL CStandardLamp::OnInit()
{
   ::CoCreateInstance(CLSID_NotifyListObject,
                      GetOwner(), 
                      CLSCTX_INPROC_SERVER,
                      IID_IUnknown,
                      (void**)&m_punkNotifyList);

   m_punkNotifyList->QueryInterface(IID_INotifySrc, 
                                    (void**)&m_pINotifySrc);
   m_RefCount-- ;
   return TRUE;   
}

::CoCreateInstance is used to create a NotifyListObject in much the same manner as the House sample created the StandardLamp object. To aggregate an object, we pass the object its controlling IUnknown. CUnknown::GetOwner returns the controlling IUnknown for CStandardLamp. Because the House sample creates CStandardLamp with a NULL for its controlling IUnknown, GetOwner returns a pointer to CStandardLamp's non-delegating IUnknown.

IUnknown* GetOwner() const
   { return m_pUnkOwner; }

::CoCreateInstance calls CNotifyListObject::CreateInstance, resulting in the construction of a CNotifyListObject. The constructor for CNotifyListObject calls the CUnknown constructor:

CUnknown::CUnknown(IUnknown* pUnk, HRESULT* phr)
{
   InterlockedIncrement(&m_ObjectCount) ;

   if (pUnk == NULL)
      m_pUnkOwner = (IUnknown*)(INonDelegatingUnknown*)this ;   
   else
      m_pUnkOwner = pUnk ;

   m_RefCount = 0 ;
}

CUnknown's constructor sets CNotifyListObject's owner to be CStandardLamp's IUnknown instead of its own non-delegating IUnknown. The rest of the construction is the same as discussed in the previous section, until CStandardLamp::OnInit queries CNotifyListObject for its INotifySrc interface:

m_punkNotifyList->QueryInterface(IID_INotifySrc, 
                                 (void**)&m_pINotifySrc);

This code calls CNotifyListObject::NonDelegatingQueryInterface, which executes the equivalent of the following code:

HRESULT CNotifyListObject::NonDelegatingQueryInterface(REFIID riid, 
                                                       void** ppv)
{
.
.
.
   if (riid == IID_INotifySrc)
   {
      *ppv = (INotifySrc*)this;
      ((IUnknown*)this)->AddRef() ;
      return NOERROR ;
   }
.
.
.
}

The code above calls CNotifyListObject::AddRef, not CNotifyListObject::NonDelegatingAddRef. The DECLARE_IUNKNOWN macro implements CNotifyListObject::AddRef as follows:

STDMETHODIMP_(ULONG) AddRef()
{
   return GetOwner()->AddRef
};

As we saw previously, CNotifyListObject::GetOwner returns a pointer to the non-delegating IUnknown for CStandardLamp, incrementing the reference count for CStandardLamp! Control then passes back to CStandardLamp::OnInit, where the reference count is decremented, because we haven't given this pointer to any outside object.

Here is the control flow in shorthand:

CStandardLamp::CreateInstance
   CUnknown::Init 
      CStandardLamp::OnInit
         CNotifyListObject::CreateInstance
            CNotifyListObject::CNotifyListObject
         .
         .
         .
         CNotifyListObject::NonDelegatingQueryInteface
            CUnknown::GetInterface
               CNotifyListObject::AddRef
                   CStandardLamp::AddRef

Conclusion

Using multiple inheritance instead of MFC to build COM objects results in source code that is much easier to develop and debug. Multiple inheritance makes COM objects more C++-like, whereas using nested classes feels like writing OLE code in C. However, not doing the implementation the "MFC way" means not being able to use AppWizard or ClassWizard. ClassWizard support for OLE Automation is a great benefit to the programmer. However, ClassWizard support for COM objects is inadequate, and not being able to use ClassWizard is no great loss for the COM object developer.

Bibliography

Thompson, Nigel. "MFC/COM Objects 1: Creating a Simple Object." March 1995. (MSDN Library, Technical Articles)

Thompson, Nigel. "MFC/COM Objects 5: Using Multiple Inheritance." March 1995. (MSDN Library, Technical Articles)

Brockschmidt, Kraig. Inside OLE 2. Redmond, WA: Microsoft Press, 1995. (MSDN Library, Books)

Special thanks to Geraint Davies for letting me look at his source code!