Click here to Skip to main content
13,898,386 members
Click here to Skip to main content
Add your own
alternative version

Tagged as

Stats

12.5K views
13 bookmarked
Posted 14 Nov 2016
Licenced CPOL

C++ Template Classes to Make COM Marshalling Simple

, 24 Nov 2016
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Leif Simon Goodwin
United Kingdom United Kingdom
C#/WPF/C++ Windows developer

You may also be interested in...

Pro

Comments and Discussions

 
GeneralMy vote of 5 Pin
MrInnocent2-Dec-16 18:43
memberMrInnocent2-Dec-16 18:43 
SuggestionSuggestion regarding your "Alternatives" paragraph Pin
Jeremy Dickman28-Nov-16 2:18
memberJeremy Dickman28-Nov-16 2:18 
GeneralRe: Suggestion regarding your "Alternatives" paragraph Pin
Leif Simon Goodwin30-Nov-16 0:43
memberLeif Simon Goodwin30-Nov-16 0:43 
GeneralMy vote of 2 Pin
KarstenK28-Nov-16 0:13
mveKarstenK28-Nov-16 0:13 
GeneralRe: My vote of 2 Pin
Leif Simon Goodwin30-Nov-16 0:44
memberLeif Simon Goodwin30-Nov-16 0:44 

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.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.190306.1 | Last Updated 25 Nov 2016
Article Copyright 2016 by Leif Simon Goodwin
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid