Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

COM in C++

0.00/5 (No votes)
14 May 2012 2  
create COM with no ATL, just C++

Introduction

This is an article about writing COM in plain C++ without ATL. When I started working on this project, I faced the problem that there are very few useful articles on the internet about writing your own COM code, so I decided to share this code in case anyone will find it useful.

The project has three components. The third one contains the two others.

Background

As I was searching a lot for nice code samples of COM objects, I guess I had to try and write one myself (not very nice I guess, but give me a chance, OK? Big Grin | :-D ). If you don't know where to start, read this article on MSDN: http://msdn.microsoft.com/en-us/library/ms683835(v=VS.85).aspx. (It is called "COM Clients and Servers" in case they move it.) I can also advise you to read Rodgerson's book "Inside COM".

Using the code 

If you are not a newbie in COM technology, then skip several of the following paragraphs as I am going to make things clear for those who are only starting to write COM code.

Let's take a look at the differences between COM objects and simple C++ classes:

Class: Uses one interface, which provides lots of C++ methods; depends on the programming language; no built-in version control. 

COM: Usually implements more than one interface; provides independence from the programming language (COM is used and implemented in different languages); built-in version control; provides independence from the location on your hard drive. 

One of the main advantages of developing in an object-oriented language such as C and Java is the ability to effectively encapsulate the internal functions and data. This is possible due to object-orientation of these languages. "Outside" the object only well-defined interface is available that allows external clients to effectively use the functionality of the object. COM technology provides these capabilities using the definition of the standard ways of implementing and providing interfaces of a COM object. 

There are two DLL projects here. Project Com2x contains implementation of two COM objects (I call them BVAA and BVAB).

The first thing was describing the interfaces. An interface is like a virtual class. You can describe it in a .h file or an .idl file using MIDL. In this example I did it in a simple .h file. Pay attention that interfaces must inherit IUknown.

COM technology provides a set of abstract classes that require implementation. When building a COM component, the first thing you need to implement is an interface that will use all the COM components: IUnknown. The component should not only implement the IUnknown interface, but also provide its implementation for each of its interfaces. At first this may seem complicated, but it is the basis of this technology. Most COM components offer multiple interfaces. Remember: A COM interface is just a pointer to a C interface.  

The IUnknown interface performs two functions. The first is to provide a standard way to query a specific interface of the component by the user (client). This feature provides a method QueryInterface. The second function is to provide a way to control the lifetime of the component from the outside. For this purpose, the IUnknown interface provides two methods: AddRef and Release, providing control of the lifetime component instance. Here is the definition of IUnknown, defined in ObjBase.h.

class IUnknown
{
public:
   virtual HRESULT QueryInterface(REFID riid, void** ppv)=0;
   virtual ULONG AddRef () = 0;
   virtual ULONG Release() = 0;
};

As IUnknown is a declaration of a COM interface, it is an abstract class. Any derived class must implement the three previously described methods and, thus add them to a virtual table. Before going any further, let's speak about the return type of the function QueryInterface, which is the type HRESULT.

Most of the methods of the COM interface and API functions return a value of type HRESULT (exceptions are AddRef and Release). In Win32, the HRESULT data type is defined as a DWORD (32-bit integer), and the return value of this type contains information about the result of the function call. The most significant bit indicates the success or erroneous shutdown function, and the next 15 bits identify the type of error and provide a way to group similar code completion, and lower 16 bits provide specific information about the incident. The HRESULT data structure is identical to that of the status flag values used by functions of the Win32 API. 

The COM Development System provides several macros to help you learn the result of calling the method. The SUCCEEDED macro returns TRUE if the function call is successful, and the FAILED macro takes the same value if the ERROR function is called. The named macros are not specific to COM and ActiveX, are used throughout the Win32 environment, and are defined in the file WINERROR.H. Return values in Win32 are prefixed S_ in the case of normal completion (for example, we use S_OK), and the prefix E_ - in case of error (E_FAILED, E_OUTOFMEMORY, etc.). 

The first step is to define the component interfaces using an abstract class like this:

//IBVAA.h 
#pragma once
#include <span class="code-keyword"><ObjBase.h>
</span>
// {5219B44A-0874-449E-8611-B7080DBFA6AB}
static const GUID IID_IBVAA_summer =
{0x5219b44a, 0x874, 0x449e, { 0x86, 0x11, 0xb7, 0x8, 0xd, 0xbf, 0xa6, 0xab} };


interface IBVAA_summer:IUnknown // summer
{
 virtual HRESULT  __stdcall Add(
	 const double x,   // [in]????????? x 
	 const double y,   // [in]????????? y
	 double& z         // [out] ????????? z = x+y
	 ) = 0;   
 virtual HRESULT  __stdcall Sub(
	 const double x,   // [in] 
	 const double y,   // [in] ?????????? y
	 double& z        // [out] ????????? z = x-y
	 ) = 0;   
};

// {8A2A00DD-8B8D-4898-B08E-000A6E40A2B5}
static const GUID IID_IBVAA_multiplier = 
{ 0x8a2a00dd, 0x8b8d, 0x4898, { 0xb0, 0x8e, 0x0, 0xa, 0x6e, 0x40, 0xa2, 0xb5 } };

interface IBVAA_multiplier:IUnknown // multiplier
{
 virtual HRESULT  __stdcall Mul(
	 const double x,   // [in]x
	 const double y,   // [in]y
	 double& z         // [out] z = x*y
	 ) = 0;   
 virtual HRESULT  __stdcall Div(
	 const double x,   // [in] x
	 const double y,   // [in] y
	 double& z         // [out] z = x/y
	 ) = 0;
};

The first class IBVAA_summer allows to add and subtract two numbers passing the result into the [out] z variable. The second one IBVAA_multiplier is to multiply and divide numbers.

One of the most powerful features of COM is that each component can provide, and generally provides multiple interfaces for a single object. Think about it again. When developing a C class, just one interface is created. And when you create a class-based component, at least one interfaceis created. Typically, when building a COM component, you need to provide multiple interfaces for a single instance of the C class.

These are interfaces that our first component (BVAA) will implement. Now let us write an interface for the second object BVAB. This interface will do power and log. 

//IBVAB.h
#pragma once
#include <span class="code-keyword"><ObjBase.h>
</span>// {59F4A881-5464-4409-9052-8C0D05828EFA}
static const GUID IID_IBVAB_power = { 0x59f4a881, 0x5464, 0x4409, { 0x90, 0x52, 0x8c, 0xd, 0x5, 0x82, 0x8e, 0xfa} };
interface IBVAB_power:IUnknown // power
{
 virtual HRESULT  __stdcall Pow(
const double x,   // [in]x 
const double y,   // [in]y
double& z          // [out] z = x^y
 ) = 0;
 virtual HRESULT  __stdcall Log(
const double x,   // [in] x
 const double y,   // [in] y
 double& z        // [out] z=log_y(x)
 ) = 0;
};

The BVAA object implements two interfaces: IBVAA_summer, IBVAA_multiplier (IBVAA.h). BVAB implements IBVAB_power (IBVAB.h).

//BVAA.h 
class BVAA : public IBVAA_summer, public IBVAA_multiplier  
//BVAB.h 
class BVAB : public IBVAB_power

We have to include .h files with our interfaces into .h files where components are described.

The implementation of a component includes the implementation of each inherited interface. The only problem with this approach is that there may be a conflict of the basic interface IUnknown (because both classes, and IBVAA and IBVAB are inherited from IUnknown), but in this case nothing happens, because the inherited classes should jointly use the implementation of the interface IUnknown. The important point here is that the COM component must provide multiple interfaces or, in other words, several pointers to virtual tables. If you provide it with the help of multiple inheritance, it is necessary that the pointer type would be correctly converted to a pointer to the virtual table. Thus, the QueryInterface method for the above class will look like:

HRESULT STDMETHODCALLTYPE BVAA::QueryInterface(REFIID riid, void  **ppv) {
HRESULT rc = S_OK;
*ppv = NULL;

//  Multiple inheritance requires an explicit cast
if       (riid == IID_IUnknown) *ppv = (IBVAA_summer*)this; 
else if  (riid == IID_IBVAA_summer)      *ppv = (IBVAA_summer*)this;
else if  (riid == IID_IBVAA_multiplier)      *ppv = (IBVAA_multiplier*)this;
else rc = E_NOINTERFACE;    

//Return a pointer to the new interface and thus call AddRef() for the new index
if (rc == S_OK) this->AddRef(); 
    return rc;
}

A pointer to the IUnknown interface can be returned by reference to either IBVAA or IBVAB, because both of these classes contain a method QueryInterface

Each COM object implements methods of IUknownIUknown is the struct defined in objbase.h, that has three methods: QueryInterface, AddRef, Release. When we write our own object we have to implement three of them. Now let us take a closer look at these methods. 

The QueryInterface method refers to the interface identifier (Interface Identifier - IID), which represents a 128-bit unique ID (i.e., GUID, which will be discussed shortly), and returns a pointer to a specific interface (for example, IUnknown, IMath), provided by the COM object. The pointer is returned by the second parameter, which is a pointer to a pointer of type void

For managing the lifetime of the components, two methods are available in the interface IUnknownAddRef() and Release(). Typically, the COM component has several interfaces, each of which may be associated with a number of external clients. Note that in this example, the component is in fact a C ++ class, and currently we are discussing lifetime management of a specific instance of the class. The user will create an instance with the help of some mechanism, which we will discuss, and take advantage of this instance through its COM interfaces. The original copy will be created by using the C++ new, and then we will try to determine when the instance can be deleted. 

As an instance of a COM component can have multiple interfaces associated with many clients, our object should have some references to the possibility of calculating it (counter). Whenever a client requests an interface, a counter value will increase, and when the client terminates the interface - it will decrease. In the end, when the counter reaches zero hits, the COM component is destroyed. For this purpose methods IUnknown::AddRef() and IUnknown::Release() have been made available. 

Thus, in this example we have to track the value of internal counter appeals. We call this counter m_lRef. When a component is available on request, the counter value will increase, and at the end of the client interface, we reduce this value by calling IUnknown::Release (). When m_lRef is 0 the components instance can be deleted. 

BVAA.h:
#pragma once
#include <span class="code-string">"IBVAA.h"
</span>class BVAA : public IBVAA_summer, public IBVAA_multiplier
{
protected:
// Reference count 
   long          m_lRef;
public:
    BVAA(void);
    ~BVAA(void);
    //IUnknown
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,void  **ppv);
    virtual ULONG   STDMETHODCALLTYPE AddRef(void);
    virtual ULONG   STDMETHODCALLTYPE Release(void);
    //IBVAA_summer
    virtual HRESULT STDMETHODCALLTYPE Add(const double x, const double y, double& z);
    virtual HRESULT STDMETHODCALLTYPE Sub(const double x, const double y, double& z); 
    //IBVAA_multiplier
    virtual HRESULT STDMETHODCALLTYPE Mul(const double x, const double y, double& z);
    virtual HRESULT STDMETHODCALLTYPE Div(const double x, const double y, double& z);
};

The next component is BVAB:

BVAB.h:
#pragma once
#include <span class="code-string">"IBVAB.h"
</span>class BVAB : public IBVAB_power
{
protected:
   // Reference count
   long          m_lRef;
public:
    BVAB(void);
    ~BVAB(void);
    //IUnknown
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,void  **ppv);
    virtual ULONG   STDMETHODCALLTYPE AddRef(void);
    virtual ULONG   STDMETHODCALLTYPE Release(void);
    //IBVAB_power
    virtual HRESULT STDMETHODCALLTYPE Pow(const double x, const double y, double& z);
    virtual HRESULT STDMETHODCALLTYPE Log(const double x, const double y, double& z);
};

When implementing these methods we will increase the reference count in AddRef(): InterlockedIncrement( &m_lRef ); and decrease it in Release(): InterlockedDecrement( &m_lRef ). In the Release() method, if we see that there are no references to the object, we should delete it:  

delete this;

To implement the functions AddRef() and Release(), inherited from the class IUnknown, we introduce a member variable m_lRef, which is responsible for counting the current hits an object has or the pending interface pointers. Although the functions AddRef() and Release() can change the value of the counter calls to the COM interface, the interface itself is not an instance of the object. An object at a time can have any number of users and their interfaces should track the value of the internal counter of active interfaces. If this value reaches zero, the object deletes itself. 

It is important to properly and timely use the methods AddRef() and Release(). This pair of functions is similar to the pair of operators new and delete, used in C for memory management. Once the user gets a new interface pointer, or assigns its value to a variable, you need to call AddRef(). In this case, you should be very careful, because some of the features of COM interfaces return pointers to other interfaces, and in such cases themselves calls the method AddRef() of the returned pointer. The most obvious example of this is the method QueryInterface(), in which AddRef() is invoked on every request interface, and thus there is no need for a new call to AddRef().

Class Factories

The file that stores the component must also contain the instruments to ensure a standard language-independent way to create instances of this component are available on client request. There is a standard COM interface IClassFactory, which should ensure the creation of instances of components on demand from outside. Below is the interface definition of IClassFactory. Like all COM interfaces, it must implement the interface IUnknown

class IClassFactory : public IUnknown
{
public:
   virtual HRESULT CreateInstance(LPUNKNOWN pUnk, REFIID riid, void** ppv)=0;
   virtual HRESULT LockServer (BOOL fLock) = 0;
};

The class factory is a COM component. The only task is to make easier the creation of other COM components. Every component, whether it is an executable or DLL-fil,e should provide the class factory implementation for each component that can be created on external demand. The main interface provides two methods of IClassFactory: CreateInstance, which creates an instance of the component, and LockServer, providing a lock server program in memory. By blocking the server for other programs, the client receives the assurance that access to it will be quickly obtained. This is usually done in order to improve performance or to keep the server in memory when it's most vulnerable; for example when it's registering itself or its components. 

Let's take a look at the interface of the class factory for component BVAA. As the BVAA and BVAB components are together in one project, I wrote a template class factory for them. Note that each component has its own class factory. In the case with this template there will be created BVAA and BVAB class factories from it.

The BVAC component is situated in another project so its has its own ClassFactory, but if you wish to, you can put them all together and use one template.

extern ULONG g_ServerLocks;     // server locks 
template <typename T>
class ClassFactory :
public IClassFactory //standard interface IClassFactory, provides creating components for outter requests
{
protected:
   // Reference count
   long          m_lRef;
public:
ClassFactory(void);
~ClassFactory(void);
//IUnknown
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,void  **ppv);
virtual ULONG   STDMETHODCALLTYPE AddRef(void);
virtual ULONG   STDMETHODCALLTYPE Release(void);
/*Basic interface IClassFactory contains 2 methods:*/
virtual HRESULT STDMETHODCALLTYPE CreateInstance(LPUNKNOWN pUnk,  const IID& id, void** ppv); //creates component instance
virtual HRESULT STDMETHODCALLTYPE LockServer (BOOL fLock);
};
 template <typename T>
HRESULT STDMETHODCALLTYPE ClassFactory<T>::CreateInstance(LPUNKNOWN pUnk,//basic IUnknown
const IID& id,// id of required interface
void** ppv)//required interface
{
HRESULT rc = E_UNEXPECTED;
//check wether we are aggregating
if (pUnk != NULL) rc = CLASS_E_NOAGGREGATION;
else {
T* p;
//creating new instance of component (T will be either BVAA or BVAB)
if ((p = new T()) == NULL) {
//return out of memory if can not create it
rc = E_OUTOFMEMORY;
}
else {
//Get a pointer from the created object to the requested interface
rc = p->QueryInterface(id,ppv);
p->Release();
}
}
return rc;
}

The class factory is required for each component and a component storage should provide instruments for the COM way to access the factory. Depending on the version of the storage, use one of two main access technologies. DLL-files must provide to the common use of two functions: DllGetClassObject and DllCanUnloadNow, executable files must register its class factory with the CoRegisterClassObject function from a library of the COM API. 

In this case, we are implementing a component of the BVAA local server. The function DllGetClassObject includes the following code: 

STDAPI DllGetClassObject(const CLSID& clsid, const IID& iid, void**ppv) { 
HRESULT rc = E_UNEXPECTED;
//we have to ensure that CLSID matches our component ID 
if (clsid == CLSID_BVAA) {
//create new ClassFactory instance 
	ClassFactory<BVAA> *cf = new ClassFactory<BVAA>();
//get it's interface 
rc = cf->QueryInterface(iid, ppv);
cf->Release();
} 
else
if (clsid == CLSID_BVAB) {
ClassFactory<BVAB> *cf = new ClassFactory<BVAB>();
rc = cf->QueryInterface(iid, ppv);
cf->Release();
}
else  
rc = CLASS_E_CLASSNOTAVAILABLE;
return rc;
};

Before the client uses any features of the COM API, he must initialize the COM services using CoInitialize. Once this is done, the client must apply the function CoGetClassObject to obtain the required interface of the class factory component (the specified identifier CLSID of the component, which will be discussed below). Immediately after receipt of the factory customer classes, it calls CreateInstance() to create an actual instance of the BVAA class and dismisses the class factory interface. 

The method CreateInstance() returns a pointer to the specified interface. In our example, we request an interface IBVAA_summer, and immediately after its receipt, we use the QueryInterface method to obtain a pointer to the interface IBVAA_multiplier (see Client listings). An interface IBVAA_multiplier will be easy to get and by calling CreateInstance, but I wanted to show you an example using the methods QueryInterface() and Release()

A pointer to the interface IBVAA_summer is used to perform some basic calculations. When we get what we want from our interfaces, we call Release() and remove the component instance. One may ask: "How do we know the storage location of the COM component (DLL)?" Response: "From the Registry." In COM, to store information on ingredients, the Windows Registry is commonly used. 

Registry 

Information about the services and COM client applications needed to host and created instances of components is stored in the Windows Registry. Turning to the Registry the application can determine the number and type of installed components, etc. 

The information in the Registry is arranged hierarchically and has several predefined high-level sections. In this chapter the most important point for us to have is a section of HKEY_CLASSES_ROOT, which stores information about the components. An important sub-section HKEY_CLASSES_ROOT is CLSID (class identifier), which describes each component installed in the system. For example, a component of BVAA we have created needs for its work several elements of the registry. Here they are: 

HKEY_CLASSES_ROOT\BVAA.Component.1 = BVA COM 
HKEY_CLASSES_ROOT\BVAA.Component.1\CurVer = BVAA.Component.1
HKEY_CLASSES_ROOT\BVAA.Component.1\CLSID = {6942E971-6F95-44BC-B3A9-EFD270EB39C9}
HKEY_CLASSES_ROOT\CLSID\{6942E971-6F95-44BC-B3A9-EFD270EB39C9} = BVAA 
HKEY_CLASSES_ROOT\CLSID\{6942E971-6F95-44BC-B3A9-EFD270EB39C9}\ProgID = BVAA.Component.1
HKEY_CLASSES_ROOT\CLSID\{6942E971-6F95-44BC-B3A9-EFD270EB39C9}\VersionIndependentProgID = Math.Component
HKEY_CLASSES_ROOT\CLSID\{6942E971-6F95-44BC-B3A9-EFD270EB39C9}\InprocServer32 = c:\PATH_TO_YOUR_PROJECT\debug\comcpp.dll
HKEY_CLASSES_ROOT\CLSID\{6942E971-6F95-44BC-B3A9-EFD270EB39C9}\NotInsertable 

We define these parameters in the Registry.h file. For registering our DLL, functions from Registry.cpp are called.

The first three lines create a programmatic identifier (ProgID) for the component of BVAA. A CLSID component is its unique identifier, but it is very difficult to read and remember. The concept is included in the COM ProgID to facilitate interaction with components of the developers. The third line provides a direct link between the ProgID of our control and the corresponding CLSID. 

In the last lines of the considered fragment of code contains all information necessary for placing the COM component in storage. 

Between the ProgID and information about the version of the component, there is a connection. However, the most important element is the section InProcServer32, which describes the exact position of the storage components. Here the basic elements of the Registry are described in detail.

ProgID Specifies the string ProgID for a COM class. It can contain 39 characters, including a decimal point.  
InProcServer32  Contains the path and name of the 32-bit DLL file. The presence of the path is optional, but if it is not specified, working with component is possible in case of placing it in a Windows dir (set the environment variable PATH). 
LocalServer32  Contains the path and name of the 32-bit EXE file. 
CurVer  ProgID of the latest version of the component class.

A good Registry viewer in terms of COM is a utility OLEVIEW, provided by the Visual C++ environment and the SDK. 

Open regedit or OLE VIEW (goes with Visual Studio, you can find it by typing OLE in Microsoft Search on your PC), and make sure registration was successful. Open the All Objects tab and find your component name (BVA), compare GUID with your one (I used the GUID generator that goes with VS2010):

The third object contains the previous two components:

BVAC.h:
#pragma once
#include <span class="code-string">"ibvac.h"
</span>#include <span class="code-string">"IBVAA.h"
</span>#include <span class="code-string">"IBVAB.h"
</span>// {347CC716-94FA-412C-8B04-AAF0116CC8F0}
static const GUID CLSID_BVAC =
{ 0x347cc716, 0x94fa, 0x412c, { 0x8b, 0x4, 0xaa, 0xf0, 0x11, 0x6c, 0xc8, 0xf0 } };
class BVAC :
    public IBVAC_moder, IBVAC_summer, IBVAC_multiplier, IBVAC_power
{
protected:
    volatile long m_lRef;
public:
    BVAC(void);
    ~BVAC(void);
    //IUnknown
    virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,void  **ppv);
    virtual ULONG   STDMETHODCALLTYPE AddRef(void);
    virtual ULONG   STDMETHODCALLTYPE Release(void);
    //IBVAC_moder
    virtual HRESULT  STDMETHODCALLTYPE Mod(const int x, const int y, int& z);
    virtual HRESULT  STDMETHODCALLTYPE Nod(const int x, const int y, int& z);
    //IBVAC_summer
    virtual HRESULT STDMETHODCALLTYPE Add(const double x, const double y, double& z);
    virtual HRESULT STDMETHODCALLTYPE Sub(const double x, const double y, double& z); 
    //IBVAC_multiplier
    virtual HRESULT STDMETHODCALLTYPE Mul(const double x, const double y, double& z);
    virtual HRESULT STDMETHODCALLTYPE Div(const double x, const double y, double& z);  
    //IBVAC_power
    virtual HRESULT STDMETHODCALLTYPE Pow(const double x, const double y, double& z);
    virtual HRESULT STDMETHODCALLTYPE Log(const double x, const double y, double& z);
    HRESULT Init();
private:
    IBVAA_summer *summer;
    IBVAA_multiplier *multiplier;
    IBVAB_power *power;
};

Function Init() is used by BVAC ClassFactory to initialize the BVAA and BVAB components:

HRESULT STDMETHODCALLTYPE BVAC_Factory::CreateInstance(LPUNKNOWN pUnk, const IID& id, void** ppv) {
    HRESULT rc = E_UNEXPECTED;
    if (pUnk != NULL) rc = CLASS_E_NOAGGREGATION;
    else if (id == IID_IBVAC_moder || id == IID_IBVAC_summer || 
             id == IID_IBVAC_power || id == IID_IBVAC_multiplier || 
             id == IID_IUnknown)
    {
        BVAC* pA;
        if ((pA = new BVAC()) == NULL)
            rc = E_OUTOFMEMORY;
        else        
            rc = pA->Init();
        if (FAILED(rc)) {
            // initialization failed, delete component
            pA->Release();
            return rc;
        }
        rc = pA->QueryInterface(id,ppv);
        pA->Release();
        return rc;
    }
    return rc;
}

When you compile your projects, register DLLs with regsvr32. Don't forget to switch to the directory with your DLL!

Then start you client and see the results of COM's work!

client

That's all

I used MSDN, "Inside COM" by Rodgerson, "Active-X Web Development" by Tom Armstrong. If you find this article useful, leave comments! I also have projects with components aggregating and EXE local server, so I will write articles about them if anyone needs them.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here