Introduction
This article is about COM and COM components. It explains some of the details behind COM technology, through implementation of a simple COM component. The article begins with some background information about COM and the reader is guided to implement and improve a simple COM component in an example. The example starts with implementation of a COM component and a COM client in a common file. The improvement of the example is about the separation of the COM component form its client. The client and the component are first separated to two different files, then the component will be put on a DLL, which can be loaded into the address space of the client and the final improvement is to register the component in Windows registry, such that the client is no longer bound to the component, and is able to create it through the class factory. In the following illustration, the relation between the component and its client is shown with a chain, which will be broken totally when the component is created through the class factory. In the last part, COM containment is explained by reusing the implemented component in a new COM component. This article is only about part one and the other parts are explained in two other articles. The demo application's code (Client + 3 component Servers) is very similar to the example explained in the article and only a window is used to visualize the component itself. The following image illustrates part one and part two:
Assumption
You are familiar with basic C++ and Windows programming.
Part one-Background
A binary standard for making software components
In object oriented programming, software objects created by different vendors cannot interact with each other if a common standard framework has not been used, and COM or Component Object Model is a binary standard for making software component language independent, and it's about breaking the applications into separate pieces. COM's implementation is hidden for users (COM clients) and it means that COM components are shipped in a binary form and are already compiled, linked and ready to use and they are just a bunch of 1s and 0s, i.e. they are just machine code, which can be executed whenever a client interact with them. Here are some advantages of COM:
- Language independency (it is possible to make COM Components with different languages).
- Reusing application architectures.
- Easy to extend functionalities of an application without rebuilding.
Functionality of COM components
An object or component in COM is any structure that exposes its functionality through the interface mechanism. In C++ applications, interfaces are defined as abstract base classes. An interface is a C++ class that contains nothing but pure virtual member functions. This means that the interface carries no implementation and only prescribes the function signatures for some other class to implement. The pure abstract base classes define the specific memory structure that COM requires for an interface, and when we define a pure abstract base class, we are actually defining the layout for a block of memory. Memory is not allocated for the structure until the abstract base class is implemented in a derived class. So in the following piece of code, IComponent
is both an interface (because its memory layout follows the COM specification) and a pure abstract base class.
interface IComponent
{
virtual void __stdcall Function1()=0;
virtual void __stdcall Function2()=0;
virtual void __stdcall Function3()=0;
};
As shown in the following image, there are two parts to the block of memory defined by a pure abstract base class which makes the interface of a COM component in C++ (the top figure illustrates Component2 from the demo application):
- The virtual function table or vtbl, which is an array of pointers that point to the implementation of the virtual functions.
- A pointer to the vtbl known as the vtbl-Pointer.
Conclusion: In COM, functionality of components is obtained through their interfaces, which are addresses or entries to components' methods.
A common need for COM objects and interfaces (Basic operations)
In a component which supports variety of functionalities and interfaces, there is a need to be able to access these interfaces easily. Another requirement is that the clients should be able to manage the existence of components and free them once they have finished using them. A COM component is a component, which supports these operations through a so-called IUnknown
interface. By inheriting and implementing of this interface, COM objects allow clients to have access to two basic operations:
- Navigating between multiple interfaces on an object through the
QueryInterface
method.
- Controlling the object�s lifetime through a reference counting mechanism handled with methods called
AddRef
and Release
.
These three methods make up the IUnknown
interface from which all other interfaces inherit. All COM interfaces must inherit from IUnknown
. This means that the first three entries in the vtbl are the same for all COM interfaces. They are the addresses for the implementation of the three methods in IUnknown
interface. So in the following code, the IComponent
interface becomes a COM interface by inheriting from IUnknown
interface:
interface IComponent :public IUnknown;
{
virtual void __stdcall Function1()=0;
virtual void __stdcall Function2()=0;
virtual void __stdcall Function3()=0;
};
Because every COM component inherits from IUnknown
, a client which has an IUnknown
interface pointer does not know what kind of interface pointer it has and it can just query for other interfaces using the QueryInterface
method. That's why this interface is called Unknown interface or IUnknown
. The following image shows the IComponent
interface being as a COM interface after inheriting from IUnknown
interface.
Conclusion: The IUnknown
interface is the fundamental interface in COM, which contains basic operations for all interfaces and objects.
The Class of COM components (Implementation)
When the component's functionality is defined through its methods (like Function1
-Function3
) by a COM interface, the component's class can be defined by deriving a class from this newly created COM interface. In the following example, this class is called CComponent
:
class CComponent:public IComponent
{
public:
virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv);
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
virtual void __stdcall Function1();
virtual void __stdcall Function2();
virtual void __stdcall Function3();
private:
long m_cRef;
};
The following image illustrates the memory layout for the CComponent
class:
As the image shows, an Interface pointer like the "this
" pointer of the CComponent
class points to the vtbl pointer. In COM, a component is accessed just through methods and never directly through variables, and pure abstract base classes have only pure virtual functions and they do not have instance data. In order to call one of the component's methods (like Function1
), the IUnknown
pointer can be used to query or ask for a pointer that points to the IComponent
interface, and by using that pointer, it will be possible to call the desired method:
IComponent* pIComponent=NULL;
pIUnknown->QueryInterface(IID_IComponent,(void**) &pIComponent;
pIComponent->Function1();
As explained before, we can navigate between interfaces on a COM object through the QueryInterface
method. Interfaces encapsulate the implementation details of components. Whenever we want to access a component, we want to obtain some desired functionality and so it is obvious that we are aware of a component's functionality, before we use it. If we do not know what functionality a component has, we cannot use it. It means that a component's client should know what kind of functionalities or interfaces the component supports. Every COM interface has an interface identifier, which can be used by clients in order to query a particular interface. The interface identifiers are 128-bit values, and in the previous piece of code, the IComponent
interface is identified by IID_IComponent
, which is the interface identifier of the IComponent
interface. So, whenever a client wants to use a functionality of a component, it should know which interface implements that functionality, and it's required that it delivers an interface identifier when it queries that interface using the QueryInterface
method. A component is comparable with a window and its interfaces with the window's menu. A window's menu operates like an interface; it is an entry to a variety of functionalities which can be obtained by selecting its items with the mouse pointer. As a matter of fact, the interfaces for the three components in the demo application are like menus, and the components methods are accessible through the menu items. The following image shows one of these components.
A window object has been used as a member data of the component in order to visualize the component, and as the image shows, the component is a window with a menu which operates as its interface, and the component's methods are accessible through menu items using the mouse pointer like an interface pointer.
Conclusion: A COM class is a particular implementation of certain interfaces.
Instantiating of COM Objects
As explained before, the only way of getting access to a component's functionality is just through its interfaces, so, once we get a pointer to a component's IUnknown
interface, we can actually access all the other interfaces (using the QueryInterface
method) supported by the component, and because all COM interfaces inherit from IUnknow
interface, every interface pointer is also an IUnknown
interface pointer. A COM object is created by using the new
operator, and an interface pointer of the created object can be obtained by converting the pointer which points to the object to an interface pointer or an IUnknown
pointer as shown below:
IUnknown* pIUnknown = static_cast<IComponent*>(new CComponent);
Interface Identifier
An interface identifier is a structure of type GUID (Globally Unique Identifier). GUIDs exist in two formats: string and numeric. In Windows registry, the string format of the GUIDs appears in various locations, however, the numeric representation of GUIDs are necessary when they are used within client applications or within the actual COM object implementation. The _GUID
structure is defined in the basetyps.h header file as shown in the following:
typedef struct _GUID
{
unsigned long Data1;
unsigned short Data2;
unsigned short Data3;
unsigned char Data4[8];
} GUID;
Your development environment will include a tool called UUIDGEN.EXE or GUIDGEN.EXE, which will give you one or more GUIDs that you can incorporate into source code. I have used GUIDGEN.EXE to create GUIDs in all the examples in this article.
An example from scratch
By now, you are able to make a simple COM object and let a client to use its functionality. Using the following steps, you may make the example, which is shown in the illustration at the beginning of the article.
You may download the code in each part and copy-paste some parts in order to make your application quickly.
Step 1:
Using the AppWizard, create a simple Win32 Console application and choose an empty project. This application will act as the COM client and will also hold the component itself.
Step 2:
Create a source file with the extension of "cpp" and give it a name. I used the name ClientAndComponent.cpp.
Step 3:
Let's decide a primitive functionality for the component, for example, the component should be able to print the phrase "COM from scratch" on the screen. As mentioned before, a COM component exposes its functionality through the interface mechanism, so the need is to make an interface, and this interface should be derived from the IUnknown
interface if the component is going to be a COM component. So, let's call the interface IComponent
, derive it from the IUnknown
interface and define the desired functionality in a method called Print
:
interface IComponent:IUnknown
{
virtual void __stdcall Print(const char* msg)=0;
};
Step 4:
In order to identify the IComponent
interface, make an interface identifier for it by using the tools UUIDGEN.EXE or GUIDGEN.EXE from your development environment:
static const IID IID_IComponent =
{ 0x853b4626, 0x393a, 0x44df,
{ 0xb1, 0x3e, 0x64, 0xca, 0xbe, 0x53, 0x5d, 0xbf } };
Step 5:
The component's class can be defined by deriving it from the defined interface (IComponent
):
class CComponent:public IComponent
{
public:
virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv);
virtual ULONG __stdcall AddRef(){return 0;}
virtual ULONG __stdcall Release(){return 0;}
virtual void __stdcall Print(const char* msg);
};
Step 6:
Now it's necessary to implement the methods:
Step 7:
Create a function in order to instantiate an object from the component class:
Step 8:
The final step is just to make the client, which uses the component's functionality through the component's interface:
void main()
{
IUnknown* pIUnknown=CreateInstance();
IComponent* pIComponent=NULL;
pIUnknown->QueryInterface(IID_IComponent,(void**)&pIComponent);
pIComponent->Print("COM from scratch.");
}
A better implementation of the IUnknown interface's methods
-
The AddRef method:
In order to control the life time of the object instantiated from the component's class, it's necessary to have a variable which could be used as a reference count. This variable should be incremented when the component is in use and decremented when the client no longer uses the component. The AddRef()
and Release()
methods can be used to increment and decrement this variable. So by adding a private member variable to the component's class and calling the AddRef()
and Release()
methods, it's possible to control the life time of the component and free the memory which has been allocated for the component, whenever this variable reaches the zero value. So in order to control the life time of the component, redefine the component's class:
class CComponent : public IComponent
{
virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) ;
virtual ULONG __stdcall AddRef();
virtual ULONG __stdcall Release();
virtual void __stdcall Print(const char* msg);
public:
CComponent() ;
~CComponent();
private:
long m_cRef ;
};
Initialize the reference count variable to zero in the constructor:
CComponent::CComponent()
{
Print("Constructing the component...") ;
m_cRef=0;
}
CComponent::~CComponent()
{
Print("Destructing the component...") ;
}
Implement the AddRef()
method:
ULONG __stdcall CComponent::AddRef()
{
Print("Incrementing the reference count variable...");
return InterlockedIncrement(&m_cRef);
}
-
The Release() method:
As mentioned, the Release()
method will be used to decrement the reference count, and in this method, if the reference count reaches zero, the component's object can be destroyed by deleting the this
pointer:
ULONG __stdcall CComponent::Release()
{
Print("Decrementing the reference count variable...");
if(InterlockedDecrement(&m_cRef) == 0)
{
delete this ;
return 0 ;
}
return m_cRef ;
}
-
QueryInterface method:
One of the limits of the QueryInterface
method in the program is that it doesn't inform their clients if an unsupported interface is queried. The HRESULT
is the key type involved in COM error reporting and is a simple 32-bit value. COM components use HRESULT
to report conditions to their clients. Like other interfaces in COM, the QueryInterface
method returns a HRESULT
. The most significant bit (severity field) of a HRESULT
reports whether the function call succeeded or failed. The last 16 bits contain the code that the function is returning. Two bits are reserved for future use and the remaining 13 bits provide more information about the type and the origin of the return code. The following image illustrates this.
So using the following implementation, the component will be able to inform its clients whether a particular interface is not supported by the component.
HRESULT __stdcall CComponent::QueryInterface(const IID& iid, void** ppv)
{
if (iid == IID_IUnknown)
{
Print("Returning pointer to IUnknown...") ;
*ppv = static_cast<IComponent*>(this) ;
}
else if (iid == IID_IComponent)
{
Print("Returning pointer to IComponent interface...") ;
*ppv = static_cast<IComponent*>(this) ;
}
else
{
Print("Interface is not supported!.") ;
*ppv = NULL ;
return E_NOINTERFACE ;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef() ;
return S_OK ;
}
Now the client is able to release the component by calling the component's Release()
method:
void main()
{
IUnknown* pIUnknown=CreateInstance();
IComponent* pIComponent=NULL;
pIUnknown->QueryInterface(IID_IComponent,(void**)&pIComponent);
pIComponent->Print("COM from scratch.");
pIComponent->Release();
}
The following screen shot is made from the example in part one:
Summary
- There are many advantages in making applications by components.
- A common standard should be used for making software components such that they become language independent, and COM is a binary standard for making software components.
- Like hardware components, some sort of functionality are assigned to software components.
- The functionality of COM components are obtained through the interface mechanism.
- In C++ applications, interfaces are defined as abstract base classes.
- The memory layout generated by the C++ compiler for a pure abstract base class is the same as the memory layout required by COM for an interface.
- As pure abstract base classes have only pure virtual functions and they do not have instance data, COM components are accessed just through their methods and never directly through member variables.
- An interface becomes a COM interface by inheriting from the
IUnknown
interface, which has three methods:
QueryInterface
, which is used in order to navigate between multiple interfaces on an object and returns a pointer to a queried interface.
AddRef
, which is used in order to control the object�s lifetime.
Release
, which is used in order to control the object�s lifetime.
- In COM, every interface pointer is also an
IUnknown
interface pointer.
- A COM component is created by using the
new
operator.
- COM components use
HRESULT
type in order to report conditions to their clients.
- Every interface is identified with an interface identifier, which is a 128 bits value with type of GUID.
- There is an important difference between a C++ class and a COM class. In C++, a class is a type, but a COM class is simply a definition of the object, and carries no type, although a C++ programmer might implement it using a C++ class.
Part two is explained in the next article.