What Is COM?
Home About Workshops Articles Writing Talks Books Contact

1. What Is COM?

1 July 2010

It happens much too often. I see far too much code that people say is COM code when it really isn't. Often people go through the motions and say that they have done this or that which makes it COM code, but it still isn't COM code because they have not followed all the rules. So here you are, I am going to tell you what makes code COM code.

Let's start with some C++ code. All of this code will compile and will run.

#include <stdio.h>
#include <objbase.h>

struct IStuff
{
   virtual HRESULT Thing() = 0;
};

class Stuff : public IStuff
{
public:
   HRESULT Thing()
   {
      printf("in Stuff::Thing\n");
      return 0;
   }
};

void main()
{
   IStuff* pObj = new Stuff;
   pObj->Thing();
   delete pObj;
}

So is Stuff a COM object? No? Why not?

The object is accessed through an interface. The abstract struct IStuff is an interface in C++, so this object is accessed through an interface. So that makes it a COM object, right? What about if I do the following, does it make you any happier?

__interface IStuff
{
   HRESULT Thing();
};

Now IStuff is an interface, right? OK so I cheated, this is a Microsoft extension to C++ and it merely means that the item is a struct and that by default the members are pure virtual.

Ah, you say, that is not a real COM interface because it does not derive from IUnknown. Persistent, aren't you? OK, lets do that too:

__interface IStuff : IUnknown
{{
    HRESULT Thing();
};

class Stuff : public IStuff
{
public:
   HRESULT Thing()   
   {
       printf("in Stuff::Thing\n");
       return 0;   
   }
   HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObject)
   {
      *ppvObject = NULL; return 0;
   }
   ULONG __stdcall AddRef()
   {
      return 0;
   }
   ULONG __stdcall Release( void)
   {
      return 0;
   }
};

void main()
{   
   IStuff* pObj = new Stuff;   
   pObj->Thing();
   pObj->Release(); // COM rules
   delete pObj;     // C++ rules
}

There you go, the interface now derives from IUnknown and the class now implements the methods, so the class is now a COM class. One COM rule is that when you have finished using an interface pointer you should call Release, so I have added code to do that. One C++ rule is that if you create objects on the C++ heap you should delete the object (and release the memory) at some point after you have finished using the objects, so I have done that too.

You're still not happy, why? Oh right, the methods do nothing, there is no reference counting and QueryInterface does not return the interfaces supported by the object, so what?

The whole point of interface programming is that you do not care how the interface is implemented. In COM you just have the guarantee that the methods are implemented, that is, there is some code there. But since you are annoyingly persistent I will add some basic code.

class Stuff : public IStuff
{
public:
   Stuff() : ulRef(0)
   {
      ulRef = 1; // well you've created it, so there must be a reference right?
   }
   HRESULT Thing()
   {
      printf("in Stuff::Thing\n"); return 0;
   }
   HRESULT __stdcall QueryInterface(REFIID riid, void** ppvObject)
   {
      *ppvObject = static_cast<void*>(this);
      this->AddRef();
      return 0;
   }
   ULONG __stdcall AddRef()
   {
      return ++ulRef;
   }
   ULONG __stdcall Release()
   {
      ULONG ref = --ulRef;
      if (ref == 0) delete this;
      return ref;
   }
private:
   ULONG ulRef;
};

There are several assumptions here. First, I am doing no error checking which you will always do (you do, don't you?). Secondly, I am assuming that QueryInterface will always succeed and that no one will call it for an interface that is not supported. Third, this code also assumes that when you have created an instance there will be a single reference count. Finally, this code is inherently single threaded, don't dare use it in a multi-threaded environment.

Since we have implemented Release to delete the object, we do not need the call to delete in the main method.

void main()
{
   IStuff* pObj = new Stuff;
   pObj->Thing();
   pObj->Release(); // COM rules
}

Since this appears to look as if we are leaking C++ heap memory, let's add a static method to the Stuff class to create an instance, here are the changes:

class Stuff : public IStuff
{
protected:
   Stuff() : ulRef(0) {  }
public:

   // Other methods...
  
   static IStuff* Create()
   {
      IStuff* pObj = new Stuff();
      pObj->AddRef();
      return pObj;
   }
private:
   ULONG ulRef;
};
void main()
{
   IStuff* pObj = Stuff::Create();
   pObj->Thing();
   pObj->Release();
}

The first thing is to provide a static method that creates an instance on the C++ heap and increments the reference count. We only want instances made through this method, so we now make the constructor protected. The interesting point is that the Create method and the Release method are intimately related. Whatever code is used to delete the object in the Release method must correspond to the code used to create the object in the Create method. Because these two methods are so intimately related I have put them in the same class. 

So this looks very COM like. But is it COM?

No. It isn't.

OK, so I am bored now. I will stop here and come back later and tell you what is wrong with that code and why it isn't COM.