Click here to Skip to main content
15,860,859 members
Articles / Programming Languages / C++
Article

COM from scratch - PART ONE

Rate me:
Please Sign up or sign in to vote.
4.89/5 (118 votes)
17 Apr 2004CPOL13 min read 290.8K   9.2K   253   62
An article about COM.

Screen shot from the demo application

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:

Summary of part1 and part2

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:

  1. Language independency (it is possible to make COM Components with different languages).
  2. Reusing application architectures.
  3. 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.

MC++
//----------------------------------------------------------------//
// Definition of component's functionallity  through an interface //
//----------------------------------------------------------------//
// The keyword "interface" is just an alias for "struct"
interface IComponent 
{
    // Methods for the implementation of the component's functionalities:
    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):

  1. The virtual function table or vtbl, which is an array of pointers that point to the implementation of the virtual functions.
  2. A pointer to the vtbl known as the vtbl-Pointer.

Component and Interface

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:

  1. Navigating between multiple interfaces on an object through the QueryInterface method.
  2. 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:

//------------------------------------------------------------//
// Being 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.

Inheritting 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:

//----------------------------------// 
//Definition of the Component's class
//----------------------------------// 
class CComponent:public IComponent
{
public:

//-------------------------------------------------------------------------//
//IUnknown methods,which are the basic oparation
//required for all COM components:
//-------------------------------------------------------------------------//
   virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv); 
   virtual ULONG __stdcall AddRef();
   virtual ULONG __stdcall Release();

//-------------------------------------------------------//   
//The new methods, which the interface supports:
//-------------------------------------------------------//
   virtual void __stdcall Function1();
   virtual void __stdcall Function2();
   virtual void __stdcall Function3();
 
private:
    // Component's member data
    long m_cRef;// The reference count variable
};

The following image illustrates the memory layout for the CComponent class:

Inheritting from IUnknown interface

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:

//---------------------------------------------//
//Getting a pointer to the IComponent interface:
//---------------------------------------------//
IComponent* pIComponent=NULL;
//The "IID_IComponent" is the interface ID for the IComponent interface 
pIUnknown->QueryInterface(IID_IComponent,(void**) &pIComponent;
// Calling the component's method:
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.

Components Interface

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:

//When new operator is used to allocate a single object,
//it gives a pointer to that object

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:

//-------------------------------------------//
// GUID definition:
//------------------------------------------//
typedef struct _GUID
 {          
    unsigned long  Data1;   // 32 bits
    unsigned short Data2;   // 16 bits
    unsigned short Data3;   // 16 bits
    unsigned char  Data4[8];// 8*8 = 64 bits
  //-----------------------------------------// 
  //    Total bits = 128 bits
} 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 definition:
//-------------------------//
interface IComponent:IUnknown
{
//The new method for assigning component's functionality
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:

//-----------------------------------------------//
 //Interface identifier, which is a 128-bit value.// 
 //-----------------------------------------------//
 // {853B4626-393A-44df-B13E-64CABE535DBF} string format
static const IID IID_IComponent = 
{ 0x853b4626, 0x393a, 0x44df, //Data1,Data2,Data3
{ 0xb1, 0x3e, 0x64, 0xca, 0xbe, 0x53, 0x5d, 0xbf } };  //Data4

Step 5:

The component's class can be defined by deriving it from the defined interface (IComponent):

//----------------------------------// 
//Definition of the Component's class
//----------------------------------// 
class CComponent:public IComponent
{
public:

//---------------------------------------------------------------------------//
//IUnknown methods,which are the basic oparation 
//required for all COM components:
//---------------------------------------------------------------------------//
   virtual HRESULT __stdcall QueryInterface(const IID& iid,void** ppv); 
   virtual ULONG __stdcall AddRef(){return 0;} 
   virtual ULONG __stdcall Release(){return 0;} 

//-------------------------------------------------------//   
//The new method, which the interface should support.
//-------------------------------------------------------//
   virtual void __stdcall Print(const char* msg);
};

Step 6:

Now it's necessary to implement the methods:

Implementation of the Component's methods

Step 7:

Create a function in order to instantiate an object from the component class:

Instantiating of Components object

Step 8:

The final step is just to make the client, which uses the component's functionality through the component's interface:

//----------------//
//   Client
//----------------//
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:

    //
    // Component
    //
    class CComponent : public IComponent
    {
        //IUnknown interface's methods:
        virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv) ;
        // method for incrementing the reference count variable "m_cRef"
        virtual ULONG __stdcall AddRef();
        // method for decrementing the reference count variable "m_cRef"
        virtual ULONG __stdcall Release();
    
        //IComponent interface's method: 
        virtual void __stdcall Print(const char* msg);
    
    public:
         CComponent() ;// Constructor
        ~CComponent();//  Destructor
    
    private:
    
        long m_cRef ;// The reference count variable
           
    };

    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.

    HRESULT type

    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 ;
        }
    
        //The reinterpret_cast operator allows any pointer to be converted into 
        //any other pointer type.
        reinterpret_cast<IUnknown*>(*ppv)->AddRef() ;
        // Incrementing the Reference count variable
        
        return S_OK ;
    }

    Now the client is able to release the component by calling the component's Release() method:

    //----------------//
    //   Client
    //----------------//
    void main()
    {
    IUnknown* pIUnknown=CreateInstance();
    IComponent* pIComponent=NULL;
    pIUnknown->QueryInterface(IID_IComponent,(void**)&pIComponent);
    pIComponent->Print("COM from scratch.");
    pIComponent->Release();// Releasing the Component
    }

The following screen shot is made from the example in part one:

The output screen shot

Summary

  1. There are many advantages in making applications by components.
  2. 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.
  3. Like hardware components, some sort of functionality are assigned to software components.
  4. The functionality of COM components are obtained through the interface mechanism.
  5. In C++ applications, interfaces are defined as abstract base classes.
  6. 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.
  7. 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.
  8. 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.
  9. In COM, every interface pointer is also an IUnknown interface pointer.
  10. A COM component is created by using the new operator.
  11. COM components use HRESULT type in order to report conditions to their clients.
  12. Every interface is identified with an interface identifier, which is a 128 bits value with type of GUID.
  13. 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Denmark Denmark
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
0x01AA9-Aug-22 2:02
mve0x01AA9-Aug-22 2:02 
BugLink broken Pin
sahil singh31-Oct-16 23:11
sahil singh31-Oct-16 23:11 
QuestionMy Vote is 5 Pin
suman43410-Jan-16 18:10
suman43410-Jan-16 18:10 
Questionisn't there a typo in the QueryInterface() implementation? Pin
kseven13-Oct-14 20:46
kseven13-Oct-14 20:46 
GeneralMy vote of 5 Pin
Member 1089941526-Jun-14 7:52
Member 1089941526-Jun-14 7:52 
QuestionAmazing way of teaching Pin
Anup Walvekar11-Oct-13 7:17
Anup Walvekar11-Oct-13 7:17 
GeneralMy vote of 5 Pin
Agnit16-May-12 20:44
Agnit16-May-12 20:44 
GeneralGeniusly explained Pin
david01118-Feb-09 22:08
david01118-Feb-09 22:08 
QuestionPage Not Found ? Pin
sandeepkavade9-Sep-08 21:14
sandeepkavade9-Sep-08 21:14 
AnswerRe: Page Not Found ? Pin
Aria Ansari10-Sep-08 7:34
Aria Ansari10-Sep-08 7:34 
GeneralAlert Add-ins for Microsoft Outlook Pin
BB13646-Dec-07 22:57
BB13646-Dec-07 22:57 
GeneralImplementation of QueryInterface Pin
bostonma16-May-06 10:30
bostonma16-May-06 10:30 
GeneralRe: Implementation of QueryInterface Pin
Zhixiang Zhu9-Jul-13 20:51
Zhixiang Zhu9-Jul-13 20:51 
GeneralQuestion.. Pin
HakunaMatada20-Apr-06 23:26
HakunaMatada20-Apr-06 23:26 
Questionhow to split into classes Pin
Anderss2222-Nov-05 2:46
Anderss2222-Nov-05 2:46 
GeneralpIComponent-&gt;AddRef() Pin
Hoang Trong Minh Tuan21-Sep-05 17:42
Hoang Trong Minh Tuan21-Sep-05 17:42 
GeneralSome questions Pin
Member 163220517-Jul-05 10:48
Member 163220517-Jul-05 10:48 
GeneralNicely done Pin
OJ9-May-05 7:45
OJ9-May-05 7:45 
GeneralCOM Events Handling Pin
Alex Rovner17-Mar-05 9:21
professionalAlex Rovner17-Mar-05 9:21 
GeneralRe: COM Events Handling Pin
Aria Ansari19-Mar-05 12:30
Aria Ansari19-Mar-05 12:30 
GeneralThis is an excellent article Pin
rogermcg17-Mar-05 0:51
rogermcg17-Mar-05 0:51 
GeneralIComponent type vs class Pin
thides6-Mar-05 12:27
thides6-Mar-05 12:27 
GeneralRe: IComponent type vs class Pin
Aria Ansari7-Mar-05 6:53
Aria Ansari7-Mar-05 6:53 
GeneralSeeing the light... Pin
Malcolm Smart14-Jan-05 15:18
Malcolm Smart14-Jan-05 15:18 
General:) Excellent Work! Pin
M AZZAAAM14-Dec-04 23:18
M AZZAAAM14-Dec-04 23:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.