Creating a simple COM+ component in C++

Update 2022.01.17: Please also check the newer, more thorough post dedicated to COM+, using modern C++ and Windows Implementation Library. Additionally, I describe in it how to troubleshoot COM clients using WinDbg. The updated source code of the example is in a GitHub repository.

Introduction

I needed to test .NET com interoperability (as all debugging APIs are written in COM+) and so decided to implement my own simple COM+ component and then use it in my .NET application. I must warn you that I just started learning COM so if you are going to use this code you need to be careful :). As I’m a big fan of Starcraft II this component will implement objects representing Nexus (Protoss main building) and Probe (Protoss harvester) – more info here. Probes can build Nexuses and Nexuses create Probes. We will have then two interfaces (INexus, IProbe) and their CoClasses (Nexus, Probe).

IDL definition

import "unknwn.idl";

[object, uuid(C5F45CBC-4439-418C-A9F9-05AC67525E43)]
interface INexus : IUnknown
{
  HRESULT CreateUnit(
    [in]  REFCLSID rclsid,
    [in]  REFIID   riid,
    [out, iid_is(riid), retval] LPVOID *ppUnk);
}

[object, uuid(246A22D5-CF02-44B2-BF09-AAB95A34E0CF)]
interface IProbe : IUnknown
{
  HRESULT ConstructBuilding(
    [in] REFCLSID rclsid,
    [in] REFIID riid,
    [out, iid_is(riid), retval] LPVOID *ppUnk);
}

We have two interfaces inherited from IUnknown as COM+ requires. Each interface contains only one method which takes reference class ID, reference interface ID and returns a newly created COM+ object. In our implementation INexus will always create an IProbe instance and IProbe will construct an INexus instance, but in the future (thanks to this very generic method definition) we may enable IProbe to construct also other buildings. Other reason was the void** pointer that I wanted to test from C# 🙂

midl.exe compilation

Once we finished our IDL file we may compile it using midl.exe application (it comes with Windows SDK):

midl.exe protoss.idl

The upper command should result in creation of four files:

  • dlldata.c – the interface registration file (it does not interest us as we will be using a .reg script)
  • protoss.h – the interface header file contains type definitions and function declarations based on the interface definition in the current IDL file (we need to include this file in server and client C++ implementations)
  • protoss_i.c – the interface UUID file collects the definitions of the interface identifiers from all interfaces in the processed IDL file (we will be using this file in our client and server c++ implementations)
  • protoss_p.c – the interface proxy file (it does not interest us as we won’t be using our component remotely)

Implementing server

On the server side we need to implement our two interfaces and additionally methods inherited from IUnknown interface. Here is our protoss_srv.cpp content:

#include <iostream>
#include "protoss.h"

using namespace std;

#include "protoss_i.c"

// {F5353C58-CFD9-4204-8D92-D274C7578B53}
const CLSID CLSID_Nexus = { 0xf5353c58, 0xcfd9, 0x4204, { 0x8d, 0x92, 0xd2, 0x74, 0xc7, 0x57, 0x8b, 0x53 } };

// {EFF8970E-C50F-45E0-9284-291CE5A6F771}
const CLSID CLSID_Probe = { 0xeff8970e, 0xc50f, 0x45e0, { 0x92, 0x84, 0x29, 0x1c, 0xe5, 0xa6, 0xf7, 0x71 } };

class Nexus : public INexus
{
public:
    // IUnknown
    ULONG __stdcall AddRef();
    ULONG __stdcall Release();
    HRESULT __stdcall QueryInterface(REFIID riid, void** ppv);

    // INexus
    HRESULT __stdcall CreateUnit(REFCLSID rclsid, REFIID riid, LPVOID *ppUnk);

    Nexus() : m_cRef(1) { }
    ~Nexus() {
      cout << "Component: Nexus::~Nexus()" << endl;
    }

private:
    ULONG m_cRef;
};

class Probe : public IProbe
{
public:
    // IUnknown
    ULONG __stdcall AddRef();
    ULONG __stdcall Release();
    HRESULT __stdcall QueryInterface(REFIID riid, void** ppv);

    // IProbe
    HRESULT STDMETHODCALLTYPE ConstructBuilding(REFCLSID rclsid, REFIID riid, LPVOID *ppUnk);

    Probe() : m_cRef(1) { }
    ~Probe() {
        cout << "Component: Probe::~Probe()" << endl;
    }
private:
    ULONG m_cRef;
};

///////////////////////////////////////////////////////////////////////////////////

ULONG __stdcall Nexus::AddRef() {
  cout << "Component: Nexus::AddRef() m_cRef = " << m_cRef + 1 << endl;
  return ++m_cRef;
}

///////////////////////////////////////////////////////////////////////////////////

ULONG __stdcall Nexus::Release() {
  cout << "Component: Nexus::Release() m_cRef = " << m_cRef - 1 << endl;
  if (--m_cRef != 0)
    return m_cRef;
  delete this;
  return 0;
}

///////////////////////////////////////////////////////////////////////////////////

HRESULT __stdcall Nexus::QueryInterface(REFIID riid, void **ppv) {
  cout << "Component: Nexus::QueryInterface" << endl;

  if (riid == IID_IUnknown) {
    *ppv = (IUnknown *)this;
  } else if (riid == IID_INexus) {
    *ppv = (INexus *)this;
  } else {
    *ppv = NULL;
    return E_NOINTERFACE;
  }
  AddRef();
  return S_OK;
}

///////////////////////////////////////////////////////////////////////////////////

HRESULT __stdcall Nexus::CreateUnit(REFCLSID rclsid, REFIID riid, LPVOID *ppUnk) {
  if (riid == IID_IProbe && rclsid == CLSID_Probe) {
    IProbe *probe = new Probe();
    *ppUnk = probe;

    return S_OK;
  }
  return E_NOINTERFACE;
}

///////////////////////////////////////////////////////////////////////////////////

ULONG __stdcall Probe::AddRef() {
  cout << "Component: Probe::AddRef() m_cRef = " << m_cRef + 1 << endl;
  return ++m_cRef;
}

///////////////////////////////////////////////////////////////////////////////////

ULONG __stdcall Probe::Release() {
  cout << "Component: Probe::Release() m_cRef = " << m_cRef - 1 << endl;
  if (--m_cRef != 0)
    return m_cRef;
  delete this;
  return 0;
}

///////////////////////////////////////////////////////////////////////////////////

HRESULT __stdcall Probe::QueryInterface(REFIID riid, void **ppv) {
  cout << "Component: Probe::QueryInterface" << endl;

  if (riid == IID_IUnknown) {
    *ppv = (IUnknown *)this;
  } else if (riid == IID_IProbe) {
    *ppv = (IProbe *)this;
  } else {
    *ppv = NULL;
    return E_NOINTERFACE;
  }
  AddRef();
  return S_OK;
}

///////////////////////////////////////////////////////////////////////////////////

HRESULT __stdcall Probe::ConstructBuilding(REFCLSID rclsid, REFIID riid, LPVOID *ppUnk) {
  if (riid == IID_INexus && rclsid == CLSID_Nexus) {
    INexus *probe = new Nexus();
    *ppUnk = probe;

    return S_OK;
  }
  return E_NOINTERFACE;
}

////////////////////////////////////////////////////////////////////////////////////

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid,
  LPVOID* ppv)
{
  if (rclsid != CLSID_Nexus && rclsid != CLSID_Probe) {
    return CLASS_E_CLASSNOTAVAILABLE;
  }

  IUnknown *pUnknown = NULL;
  if (rclsid == CLSID_Nexus) {
    pUnknown = new Nexus();
  } else if (rclsid == CLSID_Probe) {
    pUnknown = new Probe();
  }
  if (NULL == pUnknown) {
    return E_OUTOFMEMORY;
  }

  HRESULT hr = pUnknown->QueryInterface(riid, ppv);
  pUnknown->Release();

  return hr;
}

Registering component

To use the component system must be somehow aware of its existance. All COM+ classes are stored in the registry in the HKEY_CLASSES_ROOTCLSID subkey. They are identified by their class ID guids. In our case we need to add following information to the registry:

REGEDIT4

[HKEY_CLASSES_ROOT\CLSID\{F5353C58-CFD9-4204-8D92-D274C7578B53}]
@="Protoss Nexus component"

[HKEY_CLASSES_ROOT\CLSID\{F5353C58-CFD9-4204-8D92-D274C7578B53}\InprocServer32]
@="C:\com\protoss.dll"

Of course you need to change the path C:\com\protoss.dll to the directory where you put the compiled server dll.

Implementing client

The last part is the client implementation.The code is simple and short as we only want to get the INexus object:

#include <iostream>
#include "protoss.h"

#include "protoss_i.c"

using namespace std;

// {F5353C58-CFD9-4204-8D92-D274C7578B53}
const CLSID CLSID_Nexus = { 0xf5353c58, 0xcfd9, 0x4204, { 0x8d, 0x92, 0xd2, 0x74, 0xc7, 0x57, 0x8b, 0x53 } };

// {EFF8970E-C50F-45E0-9284-291CE5A6F771}
const CLSID CLSID_Probe = { 0xeff8970e, 0xc50f, 0x45e0, { 0x92, 0x84, 0x29, 0x1c, 0xe5, 0xa6, 0xf7, 0x71 } };

void __cdecl main() {

  IUnknown *pUnknown;
  INexus *pNexus;
  //IProbe *pProbe;

  HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
  if (FAILED(hr)) {
    cout << "CoInitializeEx error" << endl;
    return;
  }

  hr = CoGetClassObject(CLSID_Nexus, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, (void **)&pUnknown);
  if (FAILED(hr)) {
    cout << "CoGetClassObject error" << endl;
    return;
  }

  hr = pUnknown->QueryInterface(IID_INexus, (void **)&pNexus);
  if (FAILED(hr)) {
    cout << "Query interface failed" << endl;
    return;
  }

  pNexus->Release();

  CoUninitialize();
}

And our COM+ component should be up and running!

6 thoughts on “Creating a simple COM+ component in C++

  1. Tanvir November 28, 2013 / 10:52

    Hi,

    Nice post. Really heplful for anyone starting com coding. I have one question. Whenever I write any c++ function , I do not use __stdcall. But, I have seen when c++ is used for writing com, __stdcall is used. Can you please calrify this ?

    Thanks

    • Sebastian Solnica December 3, 2013 / 06:28

      Hi Tanvir,

      It’s a calling convention (http://msdn.microsoft.com/en-us/library/k2b2ssfy.aspx). All Windows API functions uses stdcall (http://msdn.microsoft.com/en-us/library/984x0h58.aspx) so the callee must cleanup the stack. If you call a function using __stdcall and this function expects a __cdecl (which is a default for C applications) the stack will become corrupted. In case of COM, it’s the operating system code (or rather libraries provided by MS) which call your classes and they assume that those call will be using __stdcall – that is why you need to explicitly define it.

  2. Nasif Mahbub October 28, 2019 / 06:45

    Hey, I was wondering how can I use this C++ COM (protoss.dll) from a C# client?

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.