Basic COM with Linux

Posted: April 4, 2015 in Design Patterns
Tags:

Perspective

This article is intended for C++ developers, It is assumed that the reader of this article is familiar with object oriented programming and design.

Windows C++ developers working with COM might find this article useful in leveraging their existing windows knowledge with Linux.

Introduction

While COM is widely used on windows operating systems, it is rarely used with Linux, in this article I will demonstrate a simple & Light weight Linux C++ implementation of the basic COM model.

This article is the first of a series of article discussing object oriented design using COM & C++, The Article start with a short explanation of the basic ideas and follows a simple source code example.

The core concepts are simple and are easy to implement on many platforms other than windows, at it’s very basic, COM solve two main problems, [1] Cross-module Object run-time type information, [2] Object life-cycle management, these are fundamental concepts widely used in numerous projects, COM facilitate a simple yet flexible design pattern to solve these problems.

When to use and when not to use

COM was defined decades ago, Since then, new technologies have emerged considerably reducing development cost comparing to COM, However, while these technologies have proved affective in most of the cases there are cases where performance and resource consumption are critical, in these cases C++/COM prove essential.

Web and big-data applications ( for example ) have many highly optimized frameworks enabling implementation using a higher level language such as C# or Java, however, for specialized applications where performance is critical, development must be done in C/C++, in these cases COM proves efficient, I have been vastly using COM while building multimedia / streaming engines on Windows, Linux and mobile devices.

Fundamentals

Object life-cycle control: Reference counting is used to keep the object alive as long as it is being used, and thus, each of the object consumers ( class, method, … ) increase it’s reference count while it’s using the object ( by calling ‘AddRef’ ), and, reduce the reference count when it has finished using the object ( by calling ‘Release’ ).

Object run-time type information: with COM, objects implement interfaces, each such interface is associated with a unique id, this id is then used by the object consumer ( e.g. calling method ) to query for support of a specific interface, the method implementing this logic is called QueryInterface.

The IUnknown interface

The most fundamental COM construct is the IUnknown interface, this interface must be implemented by every COM object and interface, it define methods for reference count control and run-time type information querying.

interface IUnknown
{
    virtual HRESULT QueryInterface(IN REFIID riid, OUT void** ppvObject) = 0;
    virtual UINT AddRef(void) = 0;
    virtual UINT Release(void) = 0;
};

Object life-cycle control is usually implemented using a class member variable for reference counting, calling AddRef increase the reference count by one while Release decrease the reference count, when the reference count reach zero the object is responsible to clean it-self from memory.

The QueryInterface method is used to query the object for support of a specific interface, implementation of the QueryInterface method involves iterating though the ids list of supported interfaces, if the interface queried is supported is found to be support, the object will increase it’s reference count and return a pointer reference through ‘*ppvObject’, if the queried interface was not found, E_NOINTERFACE is returned.

Implementation guidelines

Since COM object maintain their own life time using reference counting, external object life-time control should be prevented, for example allocating a COM object on the stack will cause it’s allocated resources to be released upon stack frame termination making the reference count mechanism useless and mis-leading.

To ensure the object maintain it’s own life-cycle the COM object constructors and destructor are defined as protected, this prevent the object to get directly created on the stack.

COM object creation is implemented using a special static class method usually called CreateInstance, this method allocate the object, Initialize the reference count and returns the default interface, that interface can later be used to query for other interfaces.

The Sample Code

// {00000000-0000-0000-C000-000000000046}
constexpr GUID IID_IUnknown = { 0x00000000, 0x0000, 0x0000, { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 } };

interface IUnknown
{
    virtual HRESULT QueryInterface(IN REFIID riid, OUT void** ppvObject) = 0;
    virtual UINT AddRef(void) = 0;
    virtual UINT Release(void) = 0;
};

ComBase.inl

This file contain the most basic definitions comprising basic COM behaviour, Every COM object must implement all of the IUnknown interface methods.

// {00000000-0000-0000-C000-000000000046}
constexpr GUID IID_IRefCountPrinter = { 0x12345678, 0x1234, 0x1234, { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 } };

interface IRefCountPrinter : public IUnknown
{
    virtual void PrintRefCount() = 0;
};

HRESULT CreateTester(OUT ITester** ppObj);

Interface.h

This is where we define the specialized interfaces we want our objects to support and the instance creation factory methods ( ‘CreateTexter’ in the example above ).

class TesterObj : public ITester
                , public IRefCountPrinter
{
protected:
    std::atomic_uint m_uiRefCount;

    TesterObj();
    ~TesterObj();

public:
    // IUnknown implementation
    HRESULT QueryInterface(IN REFIID riid, OUT void** ppvObject);
    UINT AddRef(void);
    UINT Release(void);

    // ITester implementation
    void TestMe();

    // IRefCountPrinter implementatin
    void PrintRefCount();

    static HRESULT CreateInstance(OUT IUnknown** ppUnk);
};

TesterObj.h

Here we define a basic COM object implementing our COM interfaces consisting of the ‘TestMe()’ and ‘PrintRefCount()’ methods, we have also defined a class variable named ‘m_uiRefCount’ to keep track of the object reference count, to support multi-threading we have to assure atomic access to the variable, for that ‘std::atomic_int’ is used.

TesterObj::TesterObj()
         : m_uiRefCount(0)
{
    printf(“TesterObj::TesterObj(), ref count = %d\n”, (UINT)m_uiRefCount);
}

TesterObj::~TesterObj()
{
    printf(“TesterObj::~TesterObj(), ref count = %d\n”, (UINT)m_uiRefCount);
}

HRESULT TesterObj::QueryInterface(IN REFIID riid, OUT void** ppvObject) {
    if (0 == memcmp(&riid, &IID_IUnknown, sizeof(GUID)))
        *ppvObject = (IUnknown*)((ITester*)this);
    else if (0 == memcmp(&riid, &IID_ITester, sizeof(GUID)))
        *ppvObject = (ITester*)this;
    else if (0 == memcmp(&riid, &IID_IRefCountPrinter, sizeof(GUID)))
        *ppvObject = (IRefCountPrinter*)this;
    else
        return E_NOINTERFACE;
    AddRef();// A reference to the object is returned via ‘*ppvObject’, add a ref
    return S_OK;
}

UINT TesterObj::AddRef(void) {
    return m_uiRefCount.fetch_add(1) + 1;
}

UINT TesterObj::Release(void) {
    const UINT uiRef = m_uiRefCount.fetch_sub(1) – 1;
    if (0 == uiRef)
        delete this;
    return uiRef;
}

void TesterObj::TestMe() {
    printf(“TesterObj::TestMe(), This is a test!\n”);
}

void TesterObj::PrintRefCount() {
    printf(“TesterObj::PrintRefCount(), Ref count is %d\n”, (UINT)m_uiRefCount);
}

HRESULT TesterObj::CreateInstance(OUT IUnknown** ppUnk) {
    TesterObj* pObj = new TesterObj();
    if (0 == pObj)
        return E_OUTOFMEMORY;
    pObj->AddRef();
    HRESULT hr = pObj->QueryInterface(IID_IUnknown, (void**)ppUnk);
    // if ‘pObj->QueryInterface’ has failed, it doesn’t increase the
    // ref count, and this ‘Release()’ will destruct the object
    pObj->Release();
    return hr;
}

TesterObj.cpp

As seen above, the AddRef and Release methods maintain the object lifetime, AddRef increase the reference count while Release decrease it, when the reference count hits zero the object clean it-self from memory.

The QueryInterface method iterate through all of the supported interfaces to verify support, if the queries interface is supported a pointer of that type is returned to the caller, note that when an interface pointer is retuned to the caller the method will increase the reference count by calling AddRef, this is done to account for the new object referenced returned, it is the responsibility of the caller to Release that reference once the object usage is done.

Instance Creation is implemented using the static method CreateInstance, AddRef bound Release the QueryInterface method to guarantee object destruction upon QueryInterface failure.

HRESULT CreateTester(OUT ITester** ppObj) {
    IUnknown* pUnk = 0;
    HRESULT hr = S_OK;
    if (FAILED(hr = TesterObj::CreateInstance(&pUnk)))
        return hr;
    hr = pUnk->QueryInterface(IID_ITester, (void**)ppObj);
    pUnk->Release();
    return hr;
}

Factory.cpp

The factory method ‘CreateTester’ simply Create the object and query it for the ITester interface.

#include “Interface.h”

int main(int argc, char *argv[])
{
    IRefCountPrinter*    pRefCntPrinter    = 0;
    ITester*            pTester            = 0;
    HRESULT                hr                = S_OK;

    if (FAILED(hr = CreateTester(&pTester))) {
        printf(“failed creating object with hr = 0x%.8x\n”, hr);
        return hr;
    }

    if (FAILED(hr = pTester->QueryInterface(IID_IRefCountPrinter, (void**)&pRefCntPrinter))) {
        printf(“failed Querying for the IRefCountPrinter interface with hr = 0x%.8x\n”, hr);
        pTester->Release();
        return hr;
    }
    pRefCntPrinter->PrintRefCount();
    pTester->TestMe();
    pTester->Release();

    printf(“Object is still alive\n”);
    pRefCntPrinter->PrintRefCount();
    pRefCntPrinter->Release();
    return 0;
}

BasicLinuxCOM.cpp

This is the main program used to instantiate and call upon the COM object methods, bellow is the execution output:

TesterObj::TesterObj(), ref count = 0
TesterObj::PrintRefCount(), Ref count is 2
TesterObj::TestMe(), This is a test!
Object is still alive
TesterObj::PrintRefCount(), Ref count is 1
TesterObj::~TesterObj(), ref count = 0

References

Component Object Model, IUnknown

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s