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

C++ Template Classes to Make COM Marshalling Simple

0.00/5 (No votes)
24 Nov 2016 1  
I have created two C++ template classes which allow a COM interface to be marshalled between threads with no fuss and no pain

Introduction

The aim of these classes is to make marshalling an interface across threads as simple and painless as possible.

Background

The code presented here is based on an article on CodeProject which explained how to marshal COM interfaces between threads.

However, whilst that article is very good, it leaves the developer to manage the lifetimes of the resources each time they wish to marshal threads. And that of course leaves room for bugs to creep in.

I decided to create some templated wrapper classes to automatically manage the lifetimes of the resource using RAII (Resource Allocation Is Initialization). The result is a very simple interface, which is much quicker to learn as you don't need to understand any of the under the hood code, unless you want to of course. And by encapsulating the resource management, it reduces the likelihood of bugs.

Using the Code

Imagine that you have an interface called IEngine. In order to marshall the interface across threads, first create an instance of the ComInterface<IEngine> class in your main thread, and ensure that it exists for as long as you need to marshall the interface. 

IEnginePtr enginePtr = LoadEngine();

ComInterface<IEngine> _engineInterface(enginePtr);

To use the interface in another thread:

ComInterfaceMarshaller<IEngine> marshaller(_engineInterface);

marshaller->StartLogging(GetLogFilePath(), VARIANT_TRUE);

That's it!

The ComInterfaceMarshaller constructor handles the marshalling for you. When you wish to use the COM interface, simply use the -> operator as shown. When the ComInterfaceMarshaller destructor is called, the associated resources are freed.

If the ComInterfaceMarshaller object is created on the main thread, there is no need to marshal the interface. This is accounted for in the code.

Discussion

Underlying the classes is the CComGITPtr class from the Active Template Library, which actually does the interface marshalling. In the ComInterfaceMarshaller constructor, the cookie which represents the original interface is converted into a marshalled interface for use by the current thread:

_gitPtr = ATL::CComGITPtr<T>(comInterface.GitCookie());

_gitPtr.CopyTo(&_interfacePtr);

And then in the ComInterfaceMarshaller destructor, the original interface is detached form the CComGITPtr instance, and the marshalled interface is released:

_gitPtr.Detach();

_interfacePtr->Release();

A COM smart ptr encapsulates the COM interface, in order to manage its lifetime, and release the interface when it is no longer needed.

The ComInterface class has a Clear method so that you can release the interface without having to wait for the destructor to be invoked, if that is required. 

Class ComInterface

#pragma once

#include "atlbase.h"

template <typename T>
class ComInterface
{
public:

    // The smart pointer class
    typedef _com_ptr_t <_com_IIID<T, &__uuidof(T)>> TPtr;

    ComInterface()
        : _interfacePtr(nullptr)
        , _ThreadId(GetCurrentThreadId())
    {

    }

    ComInterface(TPtr & interfacePtr)
        : _interfacePtr(interfacePtr)
    {
        _gitPtr = ATL::CComGITPtr<T>(interfacePtr.GetInterfacePtr());
    }

    ~ComInterface()
    {
    }

    void Clear()
    {
        _gitPtr = nullptr;
        _interfacePtr = nullptr;
    }

    unsigned long ThreadId() const
    {
        return _ThreadId;
    }

    unsigned long GitCookie() const
    {
        return _gitPtr.GetCookie();
    }

    ATL::CComGITPtr<T>& GitPtr()
    {
        return _gitPtr;
    }

    T* InterfacePtr()
    {
        return _interfacePtr.GetInterfacePtr();
    }

    TPtr& ComPtr()
    {
        return _interfacePtr;
    }

private:

    TPtr _interfacePtr;

    // This must be the ID of the thread which created the COM interface
    unsigned long _ThreadId;

    ATL::CComGITPtr<T> _gitPtr;
};

Class CComMarshaller

#pragma once
#include "ComInterface.h"

template <class T>
class ComInterfaceMarshaller
{
public:
    ComInterfaceMarshaller(ComInterface<T>& comInterface)
        : _interfacePtr(comInterface.InterfacePtr())
    {
        _sameThread = (comInterface.ThreadId() == GetCurrentThreadId());

        if (_sameThread || (_interfacePtr == nullptr))
        {
            return;
        }

        _gitPtr = ATL::CComGITPtr<T>(comInterface.GitCookie());
        _gitPtr.CopyTo(&_interfacePtr);
    }

    ~ComInterfaceMarshaller()
    {
        if (!_sameThread)
        {
            _gitPtr.Detach();
            _interfacePtr->Release();
        }
    }

    T* operator->()
    {
        return _interfacePtr;
    }

    T* GetInterface()
    {
        return _interfacePtr;
    }

private:

    ATL::CComGITPtr<T> _gitPtr;
    T* _interfacePtr;
    bool _sameThread;
};

Alternatives

I originally created these classes as I was using a Single Threaded Apartment COM server, and accessing it in a C++ DLL from multiple threads which were created by a WPF/.NET application. An alternative to marshalling interfaces across threads is to create a worker thread which alone accesses the COM server. Thus, your main thread sends requests to the worker thread, and then waits for the requests to complete. However, be aware that if you choose this approach, you will have to ensure that your main thread implements a Windows message pump, as COM uses Windows messages to serialise requests. Blocking the message pump will block your worker thread calls to the COM server, as I found to my cost.

History

  • 14th November, 2016: First version
  • 25th November, 2016: Fixed a couple of bugs in the ComInterface class. The _threadId property was not initialised correctly. The cookie was detached from _gitPtr which caused a memory leak. 

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