COM

Content:

  1. Introduction
  2. Background
  3. Examples
  4. Iteration #1 (simplest possible)
  5. Iteration #2 (methods with string arguments)
  6. Iteration #3 (use STDxxxxxx macros)
  7. Iteration #4 (IDL file)
  8. Iteration #5 (standard marshaller for non-C/C++)
  9. Iteration #6 (universal marshaller for non-C/C++)
  10. Iteration #7 (make scriptable)
  11. Iteration #8 (properties)
  12. Iteration #9 (use DispGetIDsOfNames and DispInvoke)
  13. Iteration #10 (use ATL macros) <==== this is where you want to go if you just need the result and not the calculation
  14. Iteration #11 (second interface)
  15. Registry free
  16. DCOM
  17. Final comments

Introduction:

COM is an extremely important technology on Windows. A lot of functionality rely on COM under the hood.

Also millions of ASP Classic and VB6 devcelopers used COM for their applications.

But relative few understand what COM really is.

COM is a somewhat obsolete technology - .NET provide alternative approaches for most functionality. But there are still old stuff around and cases where COM is needed for integration, so it can still be worth learning.

This article tries to explain COM from a developer perspective.

This article is heavily inspired by the book "COM and ATL 3.0" by Andrew Troelsen. The intention is not to plagiarize but to provide a shorter version with more modern examples (the book is from 2000 and the IT landscape has changed quite a bit since then).

Some familiarity with Windows and OOP in general are expected.

Background:

What is COM?

COM stands for Component Object Model.

COM is a technology that allows a program written in one language to easily call a component written in another language.

COM was created by Microsoft in 1993.

All COM components implements the interface IUnknown and has a factory implementing IClassFactory.

All scriptable COM components aka COM components callable from script languages implement the interface IDispatch socalled late binding.

Technical:

So if we have a COM interface IXxxx, an implementation CoXxxx and a factory CoXxxFactory then class diagram looks like:

COM class diagram

COM mostly use Windows registry to store lookup information (see Registry free for an alternative approach). Note that it requires privs to register a COM component.

Items get identified by GUID's.

The program calling the COM component is called the client and the COM component is called the server. That may sound weird at first, but note that COM is a general concept and it besides making an in-process call then it is also possible to call a COM component hosted in a separate process. And then the terms client and server suddenly makes more sense.

The COM interface can be described in an MIDL file and language specific headers can be generated.

Examples:

All examples will show in-process usage.

COM components can be written in many languages. But all examples will use C++ for the COM component itself. That makes sense because most COM components are written in C++.

Clients will be shown in many languages:

(the original Microsoft Java from the late 1990's had builtin support for COM, but that is long gone - today one use a library that use JNI to call COM - and Jacob is one of the common libraries for this purpose)

Everything will be done on 64 bit Windows as 32 bit code. That is probably the most common today for COM (the platform switched from 32 to 64 bit, but the COM stuff remained at 32 bit.

The client EXE and the COM DLL of course need to be the same bitwise, so here both will be 32 bit.

Note that moving to 64 bit will only mean to change the compiler setting and change the registry keys.

The example will evolve over 12 iterations.

There will be a lot of repeated code between iterations. But the concept is that each iterations section has complete code to avoid jumping forth and back.

Iteration #1:

In this iteration we will:

We use T strings in the code, because that common practice even though probably not relevant any longer (T is Unicode on WinNT and CP-1252 on Win9x).

Note that COM use reference counting. One calls AddRef when starting usage and calls Release when done and if count goes in zero then the object gets deleted.

Server:

Test.h:

#ifndef TEST_H
#define TEST_H

#include <windows.h>

// { 2f2d7587-78e8-46ee-ba65-fc7094b37a13 }
DEFINE_GUID(IID_ITest, 0x2f2d7587, 0x78e8, 0x46ee, 0xba, 0x65, 0xfc, 0x70, 0x94, 0xb3, 0x7a, 0x13);

// { 2c7de05d-a083-40d7-b7af-90af8d432489 }
DEFINE_GUID(CLSID_CoTest, 0x2c7de05d, 0xa083, 0x40d7, 0xb7, 0xaf, 0x90, 0xaf, 0x8d, 0x43, 0x24, 0x89);

interface ITest : public IUnknown
{
    virtual HRESULT __stdcall Add(long int a, long int b, long int *c) = 0;
};

class CoTest : public ITest
{
private:
    LONG volatile m_refcnt;
public:
    CoTest();
    virtual ~CoTest();
    // IUnknown
    HRESULT __stdcall QueryInterface(REFIID riid, void **ppv);
    ULONG __stdcall AddRef();
    ULONG __stdcall Release();
    // ITest
    HRESULT __stdcall Add(long int a, long int b, long int *c);
};

class CoTestFactory : IClassFactory
{
private:
    LONG volatile m_refcnt;
public:
    CoTestFactory();
    // IUnknown
    HRESULT __stdcall QueryInterface(REFIID riid, void **ppv);
    ULONG __stdcall AddRef();
    ULONG __stdcall Release();
    // IClassFactory
    HRESULT __stdcall CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv);
    HRESULT __stdcall LockServer(BOOL fLock);
};

#endif // TEST_H

Interface declare methods:

    virtual HRESULT __stdcall methodname(input_1_type input_1_name, input_2_type input_1_name, ..., result_type *result_name) 

Class declare methods:

    HRESULT __stdcall methodname(input_1_type input_1_name, input_2_type input_1_name, ..., result_type *result_name) 

Test.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include "Test.h"

static LONG volatile g_lckcnt = 0;
static LONG volatile g_objcnt = 0;

CoTest::CoTest()
{
#ifdef DEBUG
    printf("CoTest ctor\n");
#endif
    m_refcnt = 0;
    InterlockedIncrement(&g_objcnt);
}

CoTest::~CoTest()
{
#ifdef DEBUG
    printf("CoTest dtor\n");
#endif
    InterlockedDecrement(&g_objcnt);
}

HRESULT CoTest::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface IUnknown\n");
#endif
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_ITest)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface ITest\n");
#endif
        *ppv = (ITest*)this;
        ((ITest*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

ULONG CoTest::AddRef()
{
#ifdef DEBUG
    printf("CoTest AddRef\n");
#endif
    return InterlockedIncrement(&m_refcnt);
}

ULONG CoTest::Release()
{
#ifdef DEBUG
    printf("CoTest Release\n");
#endif
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

HRESULT CoTest::Add(long int a, long int b, long int *c)
{
    *c = a + b;
    return S_OK;
}

CoTestFactory::CoTestFactory()
{
    m_refcnt = 0;
}

HRESULT CoTestFactory::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_IClassFactory)
    {
        *ppv = (IClassFactory*)this;
        ((IClassFactory*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

ULONG CoTestFactory::AddRef()
{
    return InterlockedIncrement(&m_refcnt);
}

ULONG CoTestFactory::Release()
{
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

HRESULT __stdcall CoTestFactory::CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("CoTestFactory CreateInstance\n");
#endif
    if(pUnk != NULL) return CLASS_E_NOAGGREGATION;
    CoTest *pTest = new CoTest();
    HRESULT res = pTest->QueryInterface(riid, ppv);
    if(res != S_OK) delete pTest;
    return res;
}

HRESULT __stdcall CoTestFactory::LockServer(BOOL fLock)
{
    if(fLock) InterlockedIncrement(&g_lckcnt); else InterlockedDecrement(&g_lckcnt);
    return S_OK;
}

HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("DllGetClassObject\n");
#endif
    if(rclsid == CLSID_CoTest)
    {
        CoTestFactory *pTestFact = new CoTestFactory();
        HRESULT res = pTestFact->QueryInterface(riid, ppv);
        if(res != S_OK) delete pTestFact;
        return res;
    }
    else
    {
        return CLASS_E_CLASSNOTAVAILABLE;
    }
}

HRESULT __stdcall DllCanUnloadNow()
{
    if(g_lckcnt == 0 && g_objcnt == 0) return S_OK; else return S_FALSE;
}

The ITest methods are trivial. There are some complexities around the IUnknown and IClassFactory methods, but this is just boilerplate stuff.

Test.def:

LIBRARY "Test"
EXPORTS
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE

Build:

cl /LD Test.cpp Test.def
Test.reg

Test.reg:

REGEDIT
HKEY_CLASSES_ROOT\Test.CoTest\CLSID = {2c7de05d-a083-40d7-b7af-90af8d432489}
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{2c7de05d-a083-40d7-b7af-90af8d432489} = Test.CoTest
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{2c7de05d-a083-40d7-b7af-90af8d432489}\InProcServer32 = C:\Work\com\coma\Test.dll

Clients using header file:

TestCPP1.cpp:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CoGetClassObject and CreateInstance:\n");
    IClassFactory *pCF;
    res = CoGetClassObject(CLSID_CoTest, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pCF);
    ReturnCheck(_T("CoGetClassObject"), res);
    ITest *pTest;
    res = pCF->CreateInstance(NULL, IID_ITest, (void **)&pTest);
    ReturnCheck(_T("CreateInstance"), res);
    long int v;
    res = pTest->Add(123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 345 = %d\n", v);
    pTest->Release();
    pCF->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl TestCPP1.cpp ole32.lib
TestCPP1

TestCPP2.cpp:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CoCreateInstance:\n");
    ITest *pTest;
    res = CoCreateInstance(CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    long int v;
    res = pTest->Add(123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 345 = %d\n", v);
    pTest->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl TestCPP2.cpp ole32.lib
TestCPP2

What is happening:

  1. CoGetClassObject and CreateInstance / CoCreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  2. call QueryInterface to get ITest
  3. call methods on instance
  4. call Release on instance

Iteration #2:

In this iteration we will:

COM uses BSTR for strings, so we have to use SysAllocString and SysFreeString.

Server:

Test.h:

#ifndef TEST_H
#define TEST_H

#include <windows.h>

// { 2f2d7587-78e8-46ee-ba65-fc7094b37a13 }
DEFINE_GUID(IID_ITest, 0x2f2d7587, 0x78e8, 0x46ee, 0xba, 0x65, 0xfc, 0x70, 0x94, 0xb3, 0x7a, 0x13);

// { 2c7de05d-a083-40d7-b7af-90af8d432489 }
DEFINE_GUID(CLSID_CoTest, 0x2c7de05d, 0xa083, 0x40d7, 0xb7, 0xaf, 0x90, 0xaf, 0x8d, 0x43, 0x24, 0x89);

interface ITest : public IUnknown
{
    virtual HRESULT __stdcall Add(long int a, long int b, long int *c) = 0;
    virtual HRESULT __stdcall Concat(BSTR a, BSTR b, BSTR *c) = 0;
};

class CoTest : public ITest
{
private:
    LONG volatile m_refcnt;
public:
    CoTest();
    virtual ~CoTest();
    // IUnknown
    HRESULT __stdcall QueryInterface(REFIID riid, void **ppv);
    ULONG __stdcall AddRef();
    ULONG __stdcall Release();
    // ITest
    HRESULT __stdcall Add(long int a, long int b, long int *c);
    HRESULT __stdcall Concat(BSTR a, BSTR b, BSTR *c);
};

class CoTestFactory : IClassFactory
{
private:
    LONG volatile m_refcnt;
public:
    CoTestFactory();
    // IUnknown
    HRESULT __stdcall QueryInterface(REFIID riid, void **ppv);
    ULONG __stdcall AddRef();
    ULONG __stdcall Release();
    // IClassFactory
    HRESULT __stdcall CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv);
    HRESULT __stdcall LockServer(BOOL fLock);
};

#endif // TEST_H

Test.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include "Test.h"

static LONG volatile g_lckcnt = 0;
static LONG volatile g_objcnt = 0;

CoTest::CoTest()
{
#ifdef DEBUG
    printf("CoTest ctor\n");
#endif
    m_refcnt = 0;
    InterlockedIncrement(&g_objcnt);
}

CoTest::~CoTest()
{
#ifdef DEBUG
    printf("CoTest dtor\n");
#endif
    InterlockedDecrement(&g_objcnt);
}

HRESULT CoTest::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface IUnknown\n");
#endif
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_ITest)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface ITest\n");
#endif
        *ppv = (ITest*)this;
        ((ITest*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

ULONG CoTest::AddRef()
{
#ifdef DEBUG
    printf("CoTest AddRef\n");
#endif
    return InterlockedIncrement(&m_refcnt);
}

ULONG CoTest::Release()
{
#ifdef DEBUG
    printf("CoTest Release\n");
#endif
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

HRESULT CoTest::Add(long int a, long int b, long int *c)
{
    *c = a + b;
    return S_OK;
}

HRESULT CoTest::Concat(BSTR a, BSTR b, BSTR *c)
{
    OLECHAR *buf = new OLECHAR[SysStringLen(a) + SysStringLen(b) + 1];
    wcscpy(buf, a);
    wcscat(buf, b);
    *c = SysAllocString(buf);
    delete[] buf;
    return S_OK;
}

CoTestFactory::CoTestFactory()
{
    m_refcnt = 0;
}

HRESULT CoTestFactory::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_IClassFactory)
    {
        *ppv = (IClassFactory*)this;
        ((IClassFactory*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

ULONG CoTestFactory::AddRef()
{
    return InterlockedIncrement(&m_refcnt);
}

ULONG CoTestFactory::Release()
{
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

HRESULT __stdcall CoTestFactory::CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("CoTestFactory CreateInstance\n");
#endif
    if(pUnk != NULL) return CLASS_E_NOAGGREGATION;
    CoTest *pTest = new CoTest();
    HRESULT res = pTest->QueryInterface(riid, ppv);
    if(res != S_OK) delete pTest;
    return res;
}

HRESULT __stdcall CoTestFactory::LockServer(BOOL fLock)
{
    if(fLock) InterlockedIncrement(&g_lckcnt); else InterlockedDecrement(&g_lckcnt);
    return S_OK;
}

HRESULT __stdcall DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("DllGetClassObject\n");
#endif
    if(rclsid == CLSID_CoTest)
    {
        CoTestFactory *pTestFact = new CoTestFactory();
        HRESULT res = pTestFact->QueryInterface(riid, ppv);
        if(res != S_OK) delete pTestFact;
        return res;
    }
    else
    {
        return CLASS_E_CLASSNOTAVAILABLE;
    }
}

HRESULT __stdcall DllCanUnloadNow()
{
    if(g_lckcnt == 0 && g_objcnt == 0) return S_OK; else return S_FALSE;
}

Test.def:

LIBRARY "Test"
EXPORTS
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE

Build:

cl /LD Test.cpp Test.def
Test.reg

Test.reg:

REGEDIT
HKEY_CLASSES_ROOT\Test.CoTest\CLSID = {2c7de05d-a083-40d7-b7af-90af8d432489}
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{2c7de05d-a083-40d7-b7af-90af8d432489} = Test.CoTest
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{2c7de05d-a083-40d7-b7af-90af8d432489}\InProcServer32 = C:\Work\com\comb\Test.dll

Clients using header file:

TestCPP1.cpp:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CoGetClassObject and CreateInstance:\n");
    IClassFactory *pCF;
    res = CoGetClassObject(CLSID_CoTest, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pCF);
    ReturnCheck(_T("CoGetClassObject"), res);
    ITest *pTest;
    res = pCF->CreateInstance(NULL, IID_ITest, (void **)&pTest);
    ReturnCheck(_T("CreateInstance"), res);
    long int v;
    res = pTest->Add(123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 345 = %d\n", v);
    BSTR s1 = SysAllocString(L"ABC");
    BSTR s2 = SysAllocString(L"XYZ");
    BSTR s3;
    res = pTest->Concat(s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    pTest->Release();
    pCF->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl TestCPP1.cpp ole32.lib
TestCPP1

TestCPP2.cpp:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CoCreateInstance:\n");
    ITest *pTest;
    res = CoCreateInstance(CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    long int v;
    res = pTest->Add(123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 345 = %d\n", v);
    BSTR s1 = SysAllocString(L"ABC");
    BSTR s2 = SysAllocString(L"XYZ");
    BSTR s3;
    res = pTest->Concat(s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
     pTest->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl TestCPP2.cpp ole32.lib
TestCPP2

What is happening:

  1. CoGetClassObject and CreateInstance / CoCreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  2. call QueryInterface to get ITest
  3. call methods on instance
  4. call Release on instance

Iteration #3:

In this iteration we will:

Server:

Test.h:

#ifndef TEST_H
#define TEST_H

#include <windows.h>

// {57BDB1F4-6CD3-4097-B2DA-0301B72FF14D}
DEFINE_GUID(IID_ITest,  0x57bdb1f4, 0x6cd3, 0x4097, 0xb2, 0xda, 0x3, 0x1, 0xb7, 0x2f, 0xf1, 0x4d);

// {1894E8E6-3BD9-46f2-99F1-C6323DAF8F71}
DEFINE_GUID(CLSID_CoTest, 0x1894e8e6, 0x3bd9, 0x46f2, 0x99, 0xf1, 0xc6, 0x32, 0x3d, 0xaf, 0x8f, 0x71);

DECLARE_INTERFACE_(ITest, IUnknown)
{
    STDMETHOD(Add)(long int a, long int b, long int *c) PURE;
    STDMETHOD(Concat)(BSTR a, BSTR b, BSTR *c) PURE;
};

class CoTest : public ITest
{
private:
    LONG volatile m_refcnt;
public:
    CoTest();
    virtual ~CoTest();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // ITest
    STDMETHODIMP Add(long int a, long int b, long int *c);
    STDMETHODIMP Concat(BSTR a, BSTR b, BSTR *c);
};

class CoTestFactory : IClassFactory
{
private:
    LONG volatile m_refcnt;
public:
    CoTestFactory();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // IClassFactory
    STDMETHODIMP CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv);
    STDMETHODIMP LockServer(BOOL fLock);
};

#endif // TEST_H

Interface declare methods:

    STDMETHOD(methodname)(input_1_type input_1_name, input_2_type input_1_name, ..., result_type *result_name) 

Class declare methods:

    STDMETHODIMP methodname(input_1_type input_1_name, input_2_type input_1_name, ..., result_type *result_name) 

Test.cpp:

#include <stdio.h>

#include <windows.h>
#include <oleauto.h>
#include <initguid.h>

#include "Test.h"

static LONG volatile g_lckcnt = 0;
static LONG volatile g_objcnt = 0;

CoTest::CoTest()
{
#ifdef DEBUG
    printf("CoTest ctor\n");
#endif
    m_refcnt = 0;
    InterlockedIncrement(&g_objcnt);
}

CoTest::~CoTest()
{
#ifdef DEBUG
    printf("CoTest dtor\n");
#endif
    InterlockedDecrement(&g_objcnt);
}

STDMETHODIMP CoTest::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface IUnknown\n");
#endif
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_ITest)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface ITest\n");
#endif
        *ppv = (ITest*)this;
        ((ITest*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CoTest::AddRef()
{
#ifdef DEBUG
    printf("CoTest AddRef\n");
#endif
    return InterlockedIncrement(&m_refcnt);
}

STDMETHODIMP_(ULONG) CoTest::Release()
{
#ifdef DEBUG
    printf("CoTest Release\n");
#endif
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

STDMETHODIMP CoTest::Add(long int a, long int b, long int *c)
{
    *c = a + b;
    return S_OK;
}

STDMETHODIMP CoTest::Concat(BSTR a, BSTR b, BSTR *c)
{
    OLECHAR *buf = new OLECHAR[SysStringLen(a) + SysStringLen(b) + 1];
    wcscpy(buf, a);
    wcscat(buf, b);
    *c = SysAllocString(buf);
    delete[] buf;
    return S_OK;
}

CoTestFactory::CoTestFactory()
{
    m_refcnt = 0;
}

STDMETHODIMP CoTestFactory::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_IClassFactory)
    {
        *ppv = (IClassFactory*)this;
        ((IClassFactory*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CoTestFactory::AddRef()
{
    return InterlockedIncrement(&m_refcnt);
}

STDMETHODIMP_(ULONG) CoTestFactory::Release()
{
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

STDMETHODIMP CoTestFactory::CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("CoTestFactory CreateInstance\n");
#endif
    if(pUnk != NULL) return CLASS_E_NOAGGREGATION;
    CoTest *pTest = new CoTest();
    HRESULT res = pTest->QueryInterface(riid, ppv);
    if(res != S_OK) delete pTest;
    return res;
}

STDMETHODIMP CoTestFactory::LockServer(BOOL fLock)
{
    if(fLock) InterlockedIncrement(&g_lckcnt); else InterlockedDecrement(&g_lckcnt);
    return S_OK;
}

STDMETHODIMP DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("DllGetClassObject\n");
#endif
    if(rclsid == CLSID_CoTest)
    {
        CoTestFactory *pTestFact = new CoTestFactory();
        HRESULT res = pTestFact->QueryInterface(riid, ppv);
        if(res != S_OK) delete pTestFact;
        return res;
    }
    else
    {
        return CLASS_E_CLASSNOTAVAILABLE;
    }
}

STDMETHODIMP DllCanUnloadNow()
{
    if(g_lckcnt == 0 && g_objcnt == 0) return S_OK; else return S_FALSE;
}

Test.def:

LIBRARY "Test"
EXPORTS
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE

Build:

cl /LD Test.cpp Test.def
Test.reg

Test.reg:

REGEDIT
HKEY_CLASSES_ROOT\Test.CoTest\CLSID = {1894e8e6-3bd9-46f2-99f1-c6323daf8f71}
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{1894e8e6-3bd9-46f2-99f1-c6323daf8f71} = Test.CoTest
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{1894e8e6-3bd9-46f2-99f1-c6323daf8f71}\InProcServer32 = C:\Work\com\comc\Test.dll

Clients using header file:

TestCPP1.cpp:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CoGetClassObject and CreateInstance\n");
    IClassFactory *pCF;
    res = CoGetClassObject(CLSID_CoTest, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pCF);
    ReturnCheck(_T("CoGetClassObject"), res);
    ITest *pTest;
    res = pCF->CreateInstance(NULL, IID_ITest, (void **)&pTest);
    ReturnCheck(_T("CreateInstance"), res);
    long int v;
    res = pTest->Add(123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 456 = %d\n", v);
    BSTR s1 = SysAllocString(L"ABC");
    BSTR s2 = SysAllocString(L"XYZ");
    BSTR s3;
    res = pTest->Concat(s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    pTest->Release();
    pCF->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl TestCPP1.cpp ole32.lib
TestCPP1

TestCPP2.cpp:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CoCreateInstance\n");
    ITest *pTest;
    res = CoCreateInstance(CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    long int v;
    res = pTest->Add(123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 456 = %d\n", v);
    BSTR s1 = SysAllocString(L"ABC");
    BSTR s2 = SysAllocString(L"XYZ");
    BSTR s3;
    res = pTest->Concat(s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    pTest->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl TestCPP2.cpp ole32.lib
TestCPP2

What is happening:

  1. CoGetClassObject and CreateInstance / CoCreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  2. call QueryInterface to get ITest
  3. call methods on instance
  4. call Release on instance

Iteration #4:

In this iteration we will:

Server:

IDL is a special language to generate header files from.

Test.idl:

import "oaidl.idl";

// ITest
[object, uuid(CC30FE9D-8CCF-4471-BEE7-033E48D4ED06)]
interface ITest : IUnknown
{
    HRESULT Add([in] long a, [in] long b, [out,retval] long *c);
    HRESULT Concat([in] BSTR a, [in] BSTR b, [out,retval]BSTR *c);
}

// Test library
[uuid(F1ABF066-8F26-44e2-9D6E-C68A8DDF3446), version(1.0)]
library TestLibrary
{
    importlib("stdole32.tlb");
    [uuid(776C8162-CE87-45cf-8036-04E7B4ADCFF0)]
    coclass CoTest
    {
        [default] interface ITest;
    };
};

Interface declare methods:

    HRESULT methodname([in] input_1_type input_1_name, [in] input_2_type input_1_name, ..., [out,retval] result_type *result_name) 

TestEx.h:

#ifndef TESTEX_H
#define TESTEX_H

#include <windows.h>

#include "Test_i.c"
#include "Test.h"

class CoTest : public ITest
{
private:
    LONG volatile m_refcnt;
public:
    CoTest();
    virtual ~CoTest();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // ITest
    STDMETHODIMP Add(long int a, long int b, long int *c);
    STDMETHODIMP Concat(BSTR a, BSTR b, BSTR *c);
};

class CoTestFactory : IClassFactory
{
private:
    LONG volatile m_refcnt;
public:
    CoTestFactory();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // IClassFactory
    STDMETHODIMP CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv);
    STDMETHODIMP LockServer(BOOL fLock);
};

#endif // TESTEX_H

Class declare methods:

    STDMETHODIMP methodname(input_1_type input_1_name, input_2_type input_1_name, ..., result_type *result_name) 

Test.cpp:

#include <stdio.h>

#include <windows.h>
#include <oleauto.h>
#include <initguid.h>

#include "TestEx.h"

static LONG volatile g_lckcnt = 0;
static LONG volatile g_objcnt = 0;

CoTest::CoTest()
{
#ifdef DEBUG
    printf("CoTest ctor\n");
#endif
    m_refcnt = 0;
    InterlockedIncrement(&g_objcnt);
}

CoTest::~CoTest()
{
#ifdef DEBUG
    printf("CoTest dtor\n");
#endif
    InterlockedDecrement(&g_objcnt);
}

STDMETHODIMP CoTest::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface IUnknown\n");
#endif
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_ITest)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface ITest\n");
#endif
        *ppv = (ITest*)this;
        ((ITest*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CoTest::AddRef()
{
#ifdef DEBUG
    printf("CoTest AddRef\n");
#endif
    return InterlockedIncrement(&m_refcnt);
}

STDMETHODIMP_(ULONG) CoTest::Release()
{
#ifdef DEBUG
    printf("CoTest Release\n");
#endif
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

STDMETHODIMP CoTest::Add(long a, long b, long *c)
{
    *c = a + b;
    return S_OK;
}

STDMETHODIMP CoTest::Concat(BSTR a, BSTR b, BSTR *c)
{
    OLECHAR *buf = new OLECHAR[SysStringLen(a) + SysStringLen(b) + 1];
    wcscpy(buf, a);
    wcscat(buf, b);
    *c = SysAllocString(buf);
    delete[] buf;
    return S_OK;
}

CoTestFactory::CoTestFactory()
{
    m_refcnt = 0;
}

STDMETHODIMP CoTestFactory::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_IClassFactory)
    {
        *ppv = (IClassFactory*)this;
        ((IClassFactory*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CoTestFactory::AddRef()
{
    return InterlockedIncrement(&m_refcnt);
}

STDMETHODIMP_(ULONG) CoTestFactory::Release()
{
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

STDMETHODIMP CoTestFactory::CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("CoTestFactory CreateInstance\n");
#endif
    if(pUnk != NULL) return CLASS_E_NOAGGREGATION;
    CoTest *pTest = new CoTest();
    HRESULT res = pTest->QueryInterface(riid, ppv);
    if(res != S_OK) delete pTest;
    return res;
}

STDMETHODIMP CoTestFactory::LockServer(BOOL fLock)
{
    if(fLock) InterlockedIncrement(&g_lckcnt); else InterlockedDecrement(&g_lckcnt);
    return S_OK;
}

STDMETHODIMP DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("DllGetClassObject\n");
#endif
    if(rclsid == CLSID_CoTest)
    {
        CoTestFactory *pTestFact = new CoTestFactory();
        HRESULT res = pTestFact->QueryInterface(riid, ppv);
        if(res != S_OK) delete pTestFact;
        return res;
    }
    else
    {
        return CLASS_E_CLASSNOTAVAILABLE;
    }
}

STDMETHODIMP DllCanUnloadNow()
{
    if(g_lckcnt == 0 && g_objcnt == 0) return S_OK; else return S_FALSE;
}

Test.def:

LIBRARY "Test"
EXPORTS
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE

Build:

midl Test.idl
cl /LD Test.cpp Test.def oleaut32.lib
Test.reg

Test.reg:

REGEDIT
HKEY_CLASSES_ROOT\Test.CoTest\CLSID = {776c8162-ce87-45cf-8036-04e7b4adcff0}
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{776c8162-ce87-45cf-8036-04e7b4adcff0} = Test.CoTest
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{776c8162-ce87-45cf-8036-04e7b4adcff0}\InProcServer32 = C:\Work\com\comd\Test.dll
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{776c8162-ce87-45cf-8036-04e7b4adcff0}\TypeLib = {f1abf066-8f26-44e2-9d6e-c68a8ddf3446}
HKEY_CLASSES_ROOT\Wow6432Node\TypeLib\{f1abf066-8f26-44e2-9d6e-c68a8ddf3446}\1.0 = Test Library
HKEY_CLASSES_ROOT\Wow6432Node\TypeLib\{f1abf066-8f26-44e2-9d6e-c68a8ddf3446}\1.0\0\Win32 = C:\Work\com\comd\Testtype.tlb
HKEY_CLASSES_ROOT\Wow6432Node\TypeLib\{f1abf066-8f26-44e2-9d6e-c68a8ddf3446}\1.0\FLAGS = 0
HKEY_CLASSES_ROOT\Wow6432Node\TypeLib\{f1abf066-8f26-44e2-9d6e-c68a8ddf3446}\1.0\HELPDIR

Clients using header file:

TestCPP1.cpp:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#include "TestEx.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CoGetClassObject and CreateInstance\n");
    IClassFactory *pCF;
    res = CoGetClassObject(CLSID_CoTest, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pCF);
    ReturnCheck(_T("CoGetClassObject"), res);
    ITest *pTest;
    res = pCF->CreateInstance(NULL, IID_ITest, (void **)&pTest);
    ReturnCheck(_T("CreateInstance"), res);
    long int v;
    res = pTest->Add(123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 456 = %d\n", v);
    BSTR s1 = SysAllocString(L"ABC");
    BSTR s2 = SysAllocString(L"XYZ");
    BSTR s3;
    res = pTest->Concat(s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    pTest->Release();
    pCF->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP1.cpp ole32.lib oleaut32.lib
TestCPP1

TestCPP2.cpp:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#include "TestEx.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CoCreateInstance\n");
    ITest *pTest;
    res = CoCreateInstance(CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    long int v;
    res = pTest->Add(123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 456 = %d\n", v);
    BSTR s1 = SysAllocString(L"ABC");
    BSTR s2 = SysAllocString(L"XYZ");
    BSTR s3;
    res = pTest->Concat(s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    pTest->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP2.cpp ole32.lib oleaut32.lib
TestCPP2

TestC.c:

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#include <stdio.h>
#include <stdlib.h>

#define COBJMACROS
#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    ITest *pTest;
    int v;
    BSTR s1, s2, s3;
    CoInitialize(NULL);
    printf("COM/C:\n");
    res = CoCreateInstance(&CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, &IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    res = ITest_Add(pTest, 123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 345 = %d\n", v);
    s1 = SysAllocString(L"ABC");
    s2 = SysAllocString(L"XYZ");
    res = ITest_Concat(pTest, s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    ITest_Release(pTest);
    CoUninitialize();
    return 0;
}

Build and run:

cl TestC.c Test_i.c ole32.lib oleaut32.lib
TestC

What is happening:

  1. CoGetClassObject and CreateInstance / CoCreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  2. call QueryInterface to get ITest
  3. call methods on instance
  4. call Release on instance

Clients using type library:

TestCPP3.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CreateInstance:\n");
    try
    {
        ITestPtr spTest;
        res = spTest.CreateInstance(CLSID_CoTest);
        ReturnCheck(_T("CreateInstance"), res);
        long v = spTest->Add(123L, 456L);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        spTest = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP3.cpp ole32.lib oleaut32.lib
TestCPP3

TestCPP4.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with constructor:\n");
    try
    {
        ITestPtr spTest(__uuidof(CoTest));
        long v = spTest->Add(123, 456);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        spTest = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP4.cpp ole32.lib oleaut32.lib
TestCPP4

What is happening:

  1. import TLB
  2. pointer CreateInstance / pointer constructor
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  3. call QueryInterface to get ITest
  4. call methods on instance
  5. call Release on instance

Iteration #5:

In this iteration we will:

Server:

Test.idl:

import "oaidl.idl";

// ITest
[object, uuid(C5E946E6-729E-4c91-94EC-493A001C335A)]
interface ITest : IUnknown
{
    HRESULT Add([in] long a, [in] long b, [out,retval] long *c);
    HRESULT Concat([in] BSTR a, [in] BSTR b, [out,retval]BSTR *c);
}

// Test library
[uuid(6FA71121-CA28-4fab-9311-BF6EB36F9EAE), version(1.0)]
library TestLibrary
{
    importlib("stdole32.tlb");
    [uuid(7644E80B-A39E-482a-A9DD-966F48C04CEE)]
    coclass CoTest
    {
        [default] interface ITest;
    };
};

TestEx.h:

#ifndef TESTEX_H
#define TESTEX_H

#include <windows.h>

#include "Test_i.c"
#include "Test.h"

class CoTest : public ITest
{
private:
    LONG volatile m_refcnt;
public:
    CoTest();
    virtual ~CoTest();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // ITest
    STDMETHODIMP Add(long a, long b, long *c);
    STDMETHODIMP Concat(BSTR a, BSTR b, BSTR *c);
};

class CoTestFactory : IClassFactory
{
private:
    LONG volatile m_refcnt;
public:
    CoTestFactory();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // IClassFactory
    STDMETHODIMP CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv);
    STDMETHODIMP LockServer(BOOL fLock);
};

#endif // TESTEX_H

Test.cpp:

#include <stdio.h>

#include <windows.h>
#include <oleauto.h>
#include <initguid.h>

#include "TestEx.h"

static LONG volatile g_lckcnt = 0;
static LONG volatile g_objcnt = 0;

CoTest::CoTest()
{
#ifdef DEBUG
    printf("CoTest ctor\n");
#endif
    m_refcnt = 0;
    InterlockedIncrement(&g_objcnt);
}

CoTest::~CoTest()
{
#ifdef DEBUG
    printf("CoTest dtor\n");
#endif
    InterlockedDecrement(&g_objcnt);
}

STDMETHODIMP CoTest::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface IUnknown\n");
#endif
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_ITest)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface ITest\n");
#endif
        *ppv = (ITest*)this;
        ((ITest*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CoTest::AddRef()
{
#ifdef DEBUG
    printf("CoTest AddRef\n");
#endif
    return InterlockedIncrement(&m_refcnt);
}

STDMETHODIMP_(ULONG) CoTest::Release()
{
#ifdef DEBUG
    printf("CoTest Release\n");
#endif
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

STDMETHODIMP CoTest::Add(long a, long b, long *c)
{
    *c = a + b;
    return S_OK;
}

STDMETHODIMP CoTest::Concat(BSTR a, BSTR b, BSTR *c)
{
    OLECHAR *buf = new OLECHAR[SysStringLen(a) + SysStringLen(b) + 1];
    wcscpy(buf, a);
    wcscat(buf, b);
    *c = SysAllocString(buf);
    delete[] buf;
    return S_OK;
}

CoTestFactory::CoTestFactory()
{
    m_refcnt = 0;
}

STDMETHODIMP CoTestFactory::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_IClassFactory)
    {
        *ppv = (IClassFactory*)this;
        ((IClassFactory*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CoTestFactory::AddRef()
{
    return InterlockedIncrement(&m_refcnt);
}

STDMETHODIMP_(ULONG) CoTestFactory::Release()
{
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

STDMETHODIMP CoTestFactory::CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("CoTestFactory CreateInstance\n");
#endif
    if(pUnk != NULL) return CLASS_E_NOAGGREGATION;
    CoTest *pTest = new CoTest();
    HRESULT res = pTest->QueryInterface(riid, ppv);
    if(res != S_OK) delete pTest;
    return res;
}

STDMETHODIMP CoTestFactory::LockServer(BOOL fLock)
{
    if(fLock) InterlockedIncrement(&g_lckcnt); else InterlockedDecrement(&g_lckcnt);
    return S_OK;
}

STDMETHODIMP DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("DllGetClassObject\n");
#endif
    if(rclsid == CLSID_CoTest)
    {
        CoTestFactory *pTestFact = new CoTestFactory();
        HRESULT res = pTestFact->QueryInterface(riid, ppv);
        if(res != S_OK) delete pTestFact;
        return res;
    }
    else
    {
        return CLASS_E_CLASSNOTAVAILABLE;
    }
}

STDMETHODIMP DllCanUnloadNow()
{
    if(g_lckcnt == 0 && g_objcnt == 0) return S_OK; else return S_FALSE;
}

Test.def:

LIBRARY "Test"
EXPORTS
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE

TestMarshal.cpp:

// dummy to get filename correct

TestMarshal.def:

LIBRARY "TestMarshal"
EXPORTS
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE
    DllRegisterServer   PRIVATE
    DllUnregisterServer PRIVATE

Build and registration:

midl Test.idl
cl /LD Test.cpp Test.def oleaut32.lib
cl /DWIN32 /D_WIN32_WINNT=0x0500 /DREGISTER_PROXY_DLL /LD TestMarshal.cpp dlldata.c Test_p.c Test_i.c TestMarshal.def rpcrt4.lib oleaut32.lib
Test.reg
regsvr32 TestMarshal.dll

Test.reg:

REGEDIT
HKEY_CLASSES_ROOT\Test.CoTest\CLSID = {7644e80b-a39e-482a-a9dd-966f48c04cee}
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{7644e80b-a39e-482a-a9dd-966f48c04cee} = Test.CoTest
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{7644e80b-a39e-482a-a9dd-966f48c04cee}\InProcServer32 = C:\Work\com\come\Test.dll
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{7644e80b-a39e-482a-a9dd-966f48c04cee}\TypeLib = {6fa71121-ca28-4fab-9311-bf6eb36f9eae}
HKEY_CLASSES_ROOT\Wow6432Node\TypeLib\{6fa71121-ca28-4fab-9311-bf6eb36f9eae}\1.0 = Test Library
HKEY_CLASSES_ROOT\Wow6432Node\TypeLib\{6fa71121-ca28-4fab-9311-bf6eb36f9eae}\1.0\0\Win32 = C:\Work\com\come\Test.tlb
HKEY_CLASSES_ROOT\Wow6432Node\TypeLib\{6fa71121-ca28-4fab-9311-bf6eb36f9eae}\1.0\FLAGS = 0
HKEY_CLASSES_ROOT\Wow6432Node\TypeLib\{6fa71121-ca28-4fab-9311-bf6eb36f9eae}\1.0\HELPDIR

Clients using header file:

TestCPP1.cpp:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#include "TestEx.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CoGetClassObject and CreateInstance\n");
    IClassFactory *pCF;
    res = CoGetClassObject(CLSID_CoTest, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pCF);
    ReturnCheck(_T("CoGetClassObject"), res);
    ITest *pTest;
    res = pCF->CreateInstance(NULL, IID_ITest, (void **)&pTest);
    ReturnCheck(_T("CreateInstance"), res);
    long int v;
    res = pTest->Add(123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 456 = %d\n", v);
    BSTR s1 = SysAllocString(L"ABC");
    BSTR s2 = SysAllocString(L"XYZ");
    BSTR s3;
    res = pTest->Concat(s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    pTest->Release();
    pCF->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP1.cpp ole32.lib oleaut32.lib
TestCPP1

TestCPP2.cpp:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#include "TestEx.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CoCreateInstance\n");
    ITest *pTest;
    res = CoCreateInstance(CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    long int v;
    res = pTest->Add(123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 456 = %d\n", v);
    BSTR s1 = SysAllocString(L"ABC");
    BSTR s2 = SysAllocString(L"XYZ");
    BSTR s3;
    res = pTest->Concat(s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    pTest->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP2.cpp ole32.lib oleaut32.lib
TestCPP2

TestC.c:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#define COBJMACROS
#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    ITest *pTest;
    int v;
    BSTR s1, s2, s3;
    CoInitialize(NULL);
    printf("COM/C:\n");
    res = CoCreateInstance(&CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, &IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    res = ITest_Add(pTest, 123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 345 = %d\n", v);
    s1 = SysAllocString(L"ABC");
    s2 = SysAllocString(L"XYZ");
    res = ITest_Concat(pTest, s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    ITest_Release(pTest);
    CoUninitialize();
    return 0;
}

Build and run:

cl TestC.c Test_i.c ole32.lib oleaut32.lib
TestC

What is happening:

  1. CoGetClassObject and CreateInstance / CoCreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  2. call QueryInterface to get ITest
  3. call methods on instance
  4. call Release on instance

Clients using type library:

TestCPP3.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CreateInstance:\n");
    try
    {
        ITestPtr spTest;
        res = spTest.CreateInstance(CLSID_CoTest);
        ReturnCheck(_T("CreateInstance"), res);
        long v = spTest->Add(123L, 456L);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        spTest = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP3.cpp ole32.lib oleaut32.lib
TestCPP3

TestCPP4.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with constructor:\n");
    try
    {
        ITestPtr spTest(__uuidof(CoTest));
        long v = spTest->Add(123, 456);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        spTest = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP4.cpp ole32.lib oleaut32.lib
TestCPP4

TestCS.cpp:

using System;

using Test;

public class MainClass
{
    public static void Main(string[] args)
    {
        Console.WriteLine("COM/C# static");
        CoTest cotest = new CoTestClass();
        ITest test = (ITest)cotest;
        Console.WriteLine("123 + 456 = {0}", test.Add(123, 456));
        Console.WriteLine("ABC + XYZ = {0}", test.Concat("ABC", "XYZ"));
    }
}

Build and run:

tlbimp /out=TestWrap.dll /namespace:Test Test.tlb
csc /platform:x86 /r:TestWrap.dll TestCS.cs
TestCS

What is happening:

  1. import TLB
  2. pointer CreateInstance / pointer constructor / wrapper constructor
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  3. call QueryInterface to get ITest
  4. call methods on instance
  5. call Release on instance

Iteration #6:

In this iteration we will:

Server:

Test.idl:

import "oaidl.idl";

// ITest
[object, uuid(41AD7CCD-365F-42ff-BA57-F4E6849D418F), oleautomation]
interface ITest : IUnknown
{
    HRESULT Add([in] long a, [in] long b, [out,retval] long *c);
    HRESULT Concat([in] BSTR a, [in] BSTR b, [out,retval]BSTR *c);
}

// Test library
[uuid(BBA1FF63-7EA0-463f-85C6-42E1105BCA71), version(1.0)]
library TestLibrary
{
    importlib("stdole32.tlb");
    [uuid(F658A1B1-E5A2-4f12-8054-B6167930E9EC)]
    coclass CoTest
    {
        [default] interface ITest;
    };
};

TestEx.h:

#ifndef TESTEX_H
#define TESTEX_H

#include <windows.h>

#include "Test_i.c"
#include "Test.h"

class CoTest : public ITest
{
private:
    LONG volatile m_refcnt;
public:
    CoTest();
    virtual ~CoTest();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // ITest
    STDMETHODIMP Add(long a, long b, long *c);
    STDMETHODIMP Concat(BSTR a, BSTR b, BSTR *c);
};

class CoTestFactory : IClassFactory
{
private:
    LONG volatile m_refcnt;
public:
    CoTestFactory();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // IClassFactory
    STDMETHODIMP CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv);
    STDMETHODIMP LockServer(BOOL fLock);
};

#endif // TESTEX_H

Test.cpp:

#include <stdio.h>

#include <windows.h>
#include <oleauto.h>
#include <initguid.h>

#include "TestEx.h"

static LONG volatile g_lckcnt = 0;
static LONG volatile g_objcnt = 0;

CoTest::CoTest()
{
#ifdef DEBUG
    printf("CoTest ctor\n");
#endif
    m_refcnt = 0;
    InterlockedIncrement(&g_objcnt);
}

CoTest::~CoTest()
{
#ifdef DEBUG
    printf("CoTest dtor\n");
#endif
    InterlockedDecrement(&g_objcnt);
}

STDMETHODIMP CoTest::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface IUnknown\n");
#endif
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_ITest)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface ITest\n");
#endif
        *ppv = (ITest*)this;
        ((ITest*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CoTest::AddRef()
{
#ifdef DEBUG
    printf("CoTest AddRef\n");
#endif
    return InterlockedIncrement(&m_refcnt);
}

STDMETHODIMP_(ULONG) CoTest::Release()
{
#ifdef DEBUG
    printf("CoTest Release\n");
#endif
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

STDMETHODIMP CoTest::Add(long a, long b, long *c)
{
    *c = a + b;
    return S_OK;
}

STDMETHODIMP CoTest::Concat(BSTR a, BSTR b, BSTR *c)
{
    OLECHAR *buf = new OLECHAR[SysStringLen(a) + SysStringLen(b) + 1];
    wcscpy(buf, a);
    wcscat(buf, b);
    *c = SysAllocString(buf);
    delete[] buf;
    return S_OK;
}

CoTestFactory::CoTestFactory()
{
    m_refcnt = 0;
}

STDMETHODIMP CoTestFactory::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_IClassFactory)
    {
        *ppv = (IClassFactory*)this;
        ((IClassFactory*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CoTestFactory::AddRef()
{
    return InterlockedIncrement(&m_refcnt);
}

STDMETHODIMP_(ULONG) CoTestFactory::Release()
{
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

STDMETHODIMP CoTestFactory::CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("CoTestFactory CreateInstance\n");
#endif
    if(pUnk != NULL) return CLASS_E_NOAGGREGATION;
    CoTest *pTest = new CoTest();
    HRESULT res = pTest->QueryInterface(riid, ppv);
    if(res != S_OK) delete pTest;
    return res;
}

STDMETHODIMP CoTestFactory::LockServer(BOOL fLock)
{
    if(fLock) InterlockedIncrement(&g_lckcnt); else InterlockedDecrement(&g_lckcnt);
    return S_OK;
}

STDMETHODIMP DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("DllGetClassObject\n");
#endif
    if(rclsid == CLSID_CoTest)
    {
        CoTestFactory *pTestFact = new CoTestFactory();
        HRESULT res = pTestFact->QueryInterface(riid, ppv);
        if(res != S_OK) delete pTestFact;
        return res;
    }
    else
    {
        return CLASS_E_CLASSNOTAVAILABLE;
    }
}

STDMETHODIMP DllCanUnloadNow()
{
    if(g_lckcnt == 0 && g_objcnt == 0) return S_OK; else return S_FALSE;
}

STDMETHODIMP DllRegisterServer()
{
    ITypeLib *pTLib = NULL;
    LoadTypeLibEx(L"Test.tlb", REGKIND_REGISTER, &pTLib);
    pTLib->Release();
    return S_OK;
}

STDMETHODIMP DllUnregisterServer()
{
    UnRegisterTypeLib(LIBID_TestLibrary, 1, 0, LANG_NEUTRAL, SYS_WIN32);
    return S_OK;
}

Test.def:

LIBRARY "Test"
EXPORTS
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE
    DllRegisterServer   PRIVATE
    DllUnregisterServer   PRIVATE

Build and registration:

midl Test.idl
cl /LD Test.cpp Test.def oleaut32.lib
Test.reg
regsvr32 Test.dll

Test.reg:

REGEDIT
HKEY_CLASSES_ROOT\Test.CoTest\CLSID = {f658a1b1-e5a2-4f12-8054-b6167930e9ec}
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{f658a1b1-e5a2-4f12-8054-b6167930e9ec} = Test.CoTest
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{f658a1b1-e5a2-4f12-8054-b6167930e9ec}\InProcServer32 = C:\Work\com\comf\Test.dll
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{f658a1b1-e5a2-4f12-8054-b6167930e9ec}\TypeLib = {bba1ff63-7ea0-463f-85c6-42e1105bca71}

Clients using header file:

TestCPP1.cpp:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#include "TestEx.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CoGetClassObject and CreateInstance\n");
    IClassFactory *pCF;
    res = CoGetClassObject(CLSID_CoTest, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pCF);
    ReturnCheck(_T("CoGetClassObject"), res);
    ITest *pTest;
    res = pCF->CreateInstance(NULL, IID_ITest, (void **)&pTest);
    ReturnCheck(_T("CreateInstance"), res);
    long int v;
    res = pTest->Add(123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 456 = %d\n", v);
    BSTR s1 = SysAllocString(L"ABC");
    BSTR s2 = SysAllocString(L"XYZ");
    BSTR s3;
    res = pTest->Concat(s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    pTest->Release();
    pCF->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP1.cpp ole32.lib oleaut32.lib
TestCPP1

TestCPP2.cpp:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#include "TestEx.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CoCreateInstance\n");
    ITest *pTest;
    res = CoCreateInstance(CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    long int v;
    res = pTest->Add(123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 456 = %d\n", v);
    BSTR s1 = SysAllocString(L"ABC");
    BSTR s2 = SysAllocString(L"XYZ");
    BSTR s3;
    res = pTest->Concat(s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    pTest->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP2.cpp ole32.lib oleaut32.lib
TestCPP2

TestC.c:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#define COBJMACROS
#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    ITest *pTest;
    int v;
    BSTR s1, s2, s3;
    CoInitialize(NULL);
    printf("COM/C:\n");
    res = CoCreateInstance(&CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, &IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    res = ITest_Add(pTest, 123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 345 = %d\n", v);
    s1 = SysAllocString(L"ABC");
    s2 = SysAllocString(L"XYZ");
    res = ITest_Concat(pTest, s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    ITest_Release(pTest);
    CoUninitialize();
    return 0;
}

Build and run:

cl TestC.c Test_i.c ole32.lib oleaut32.lib
TestC

What is happening:

  1. CoGetClassObject and CreateInstance / CoCreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  2. call QueryInterface to get ITest
  3. call methods on instance
  4. call Release on instance

Clients using type library:

TestCPP3.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CreateInstance:\n");
    try
    {
        ITestPtr spTest;
        res = spTest.CreateInstance(CLSID_CoTest);
        ReturnCheck(_T("CreateInstance"), res);
        long v = spTest->Add(123L, 456L);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        spTest = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP3.cpp ole32.lib oleaut32.lib
TestCPP3

TestCPP4.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with constructor:\n");
    try
    {
        ITestPtr spTest(__uuidof(CoTest));
        long v = spTest->Add(123, 456);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        spTest = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP4.cpp ole32.lib oleaut32.lib
TestCPP4

TestCS.cpp:

using System;

using Test;

public class MainClass
{
    public static void Main(string[] args)
    {
        Console.WriteLine("COM/C# static");
        CoTest cotest = new CoTestClass();
        ITest test = (ITest)cotest;
        Console.WriteLine("123 + 456 = {0}", test.Add(123, 456));
        Console.WriteLine("ABC + XYZ = {0}", test.Concat("ABC", "XYZ"));
    }
}

Build and run:

tlbimp /out=TestWrap.dll /namespace:Test Test.tlb
csc /platform:x86 /r:TestWrap.dll TestCS.cs
TestCS

What is happening:

  1. import TLB
  2. pointer CreateInstance / pointer constructor / wrapper constructor
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  3. call QueryInterface to get ITest
  4. call methods on instance
  5. call Release on instance

Iteration #7:

In this iteration we will:

Server:

Test.idl:

import "oaidl.idl";

// ITest
[object, uuid(40237A9C-9766-4097-AA67-2844AC846A6F), oleautomation, dual]
interface ITest : IDispatch
{
    [id(1)] HRESULT Add([in] long a, [in] long b, [out,retval] long *c);
    [id(2)] HRESULT Concat([in] BSTR a, [in] BSTR b, [out,retval]BSTR *c);
}

// Test library
[uuid(CC9EC8D0-1AAA-4e32-97A3-9E875EB7D744), version(1.0)]
library TestLibrary
{
    importlib("stdole32.tlb");
    [uuid(FC2AE506-88F3-4f00-9755-76768114617A)]
    coclass CoTest
    {
        [default] interface ITest;
    };
};

Interface declare methods:

    [id(membernumber)] HRESULT methodname([in] input_1_type input_1_name, [in] input_2_type input_1_name, ..., [out,retval] result_type *result_name) 

TestEx.h:

#ifndef TESTEX_H
#define TESTEX_H

#include <windows.h>

#include "Test_i.c"
#include "Test.h"

class CoTest : public ITest
{
private:
    LONG volatile m_refcnt;
public:
    CoTest();
    virtual ~CoTest();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // IDispatch
    STDMETHODIMP GetTypeInfoCount(UINT *pctinfo);
    STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo);
    STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
    STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExceptInfo, UINT *puArgErr);
    // ITest
    STDMETHODIMP Add(long a, long b, long *c);
    STDMETHODIMP Concat(BSTR a, BSTR b, BSTR *c);
};

class CoTestFactory : IClassFactory
{
private:
    LONG volatile m_refcnt;
public:
    CoTestFactory();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // IClassFactory
    STDMETHODIMP CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv);
    STDMETHODIMP LockServer(BOOL fLock);
};

#endif // TESTEX_H

Test.cpp:

#include <stdio.h>

#include <windows.h>
#include <oleauto.h>
#include <initguid.h>

#include "TestEx.h"

#define DISPID_ADD 1
#define DISPID_CONCAT 2

static LONG volatile g_lckcnt = 0;
static LONG volatile g_objcnt = 0;

CoTest::CoTest()
{
#ifdef DEBUG
    printf("CoTest ctor\n");
#endif
    m_refcnt = 0;
    InterlockedIncrement(&g_objcnt);
}

CoTest::~CoTest()
{
#ifdef DEBUG
    printf("CoTest dtor\n");
#endif
    InterlockedDecrement(&g_objcnt);
}

STDMETHODIMP CoTest::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface IUnknown\n");
#endif
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_IDispatch)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface IDispatch\n");
#endif
        *ppv = (IDispatch*)this;
        ((ITest*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_ITest)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface ITest\n");
#endif
        *ppv = (ITest*)this;
        ((ITest*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CoTest::AddRef()
{
#ifdef DEBUG
    printf("CoTest AddRef\n");
#endif
    return InterlockedIncrement(&m_refcnt);
}

STDMETHODIMP_(ULONG) CoTest::Release()
{
#ifdef DEBUG
    printf("CoTest Release\n");
#endif
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

STDMETHODIMP CoTest::GetTypeInfoCount(UINT *pctinfo)
{
    *pctinfo = 0;
    return S_OK;
}

STDMETHODIMP CoTest::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
{
    *ppTInfo = NULL;
    return E_NOTIMPL;
}

STDMETHODIMP CoTest::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
    // max. 1 name per call
    if(cNames > 1) return E_INVALIDARG;
    if(_wcsicmp(rgszNames[0], L"Add") == 0)
    {
#ifdef DEBUG
        printf("CoTest GetIDsOfNames Add\n");
#endif
        *rgDispId = DISPID_ADD;
    }
    else if(_wcsicmp(rgszNames[0], L"Concat") == 0)
    {
#ifdef DEBUG
        printf("CoTest GetIDsOfNames Concat\n");
#endif
        *rgDispId = DISPID_CONCAT;
    }
    else
    {
        return DISP_E_UNKNOWNNAME;
    }
    return S_OK;
}

STDMETHODIMP CoTest::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExceptInfo, UINT *puArgErr)
{
    switch(dispIdMember)
    {
        case DISPID_ADD:
#ifdef DEBUG
            printf("CoTest Invoke Add\n");
#endif
            long ltmp;
            Add(pDispParams->rgvarg[1].lVal, pDispParams->rgvarg[0].lVal, <mp);
            pVarResult->vt = VT_I4;
            pVarResult->lVal = ltmp;
            return S_OK;
        case DISPID_CONCAT:
#ifdef DEBUG
            printf("CoTest Invoke Concat\n");
#endif
            BSTR bstrtmp;
            Concat(pDispParams->rgvarg[1].bstrVal, pDispParams->rgvarg[0].bstrVal, &bstrtmp);
            pVarResult->vt = VT_BSTR;
            pVarResult->bstrVal = bstrtmp;
            return S_OK;
        default:
            return DISP_E_UNKNOWNINTERFACE;
    }
}

STDMETHODIMP CoTest::Add(long a, long b, long *c)
{
    *c = a + b;
    return S_OK;
}

STDMETHODIMP CoTest::Concat(BSTR a, BSTR b, BSTR *c)
{
    OLECHAR *buf = new OLECHAR[SysStringLen(a) + SysStringLen(b) + 1];
    wcscpy(buf, a);
    wcscat(buf, b);
    *c = SysAllocString(buf);
    delete[] buf;
    return S_OK;
}

CoTestFactory::CoTestFactory()
{
    m_refcnt = 0;
}

STDMETHODIMP CoTestFactory::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_IClassFactory)
    {
        *ppv = (IClassFactory*)this;
        ((IClassFactory*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CoTestFactory::AddRef()
{
    return InterlockedIncrement(&m_refcnt);
}

STDMETHODIMP_(ULONG) CoTestFactory::Release()
{
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

STDMETHODIMP CoTestFactory::CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("CoTestFactory CreateInstance\n");
#endif
    if(pUnk != NULL) return CLASS_E_NOAGGREGATION;
    CoTest *pTest = new CoTest();
    HRESULT res = pTest->QueryInterface(riid, ppv);
    if(res != S_OK) delete pTest;
    return res;
}

STDMETHODIMP CoTestFactory::LockServer(BOOL fLock)
{
    if(fLock) InterlockedIncrement(&g_lckcnt); else InterlockedDecrement(&g_lckcnt);
    return S_OK;
}

STDMETHODIMP DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("DllGetClassObject\n");
#endif
    if(rclsid == CLSID_CoTest)
    {
        CoTestFactory *pTestFact = new CoTestFactory();
        HRESULT res = pTestFact->QueryInterface(riid, ppv);
        if(res != S_OK) delete pTestFact;
        return res;
    }
    else
    {
        return CLASS_E_CLASSNOTAVAILABLE;
    }
}

STDMETHODIMP DllCanUnloadNow()
{
    if(g_lckcnt == 0 && g_objcnt == 0) return S_OK; else return S_FALSE;
}

STDMETHODIMP DllRegisterServer()
{
    ITypeLib *pTLib = NULL;
    LoadTypeLibEx(L"Test.tlb", REGKIND_REGISTER, &pTLib);
    pTLib->Release();
    return S_OK;
}

STDMETHODIMP DllUnregisterServer()
{
    UnRegisterTypeLib(LIBID_TestLibrary, 1, 0, LANG_NEUTRAL, SYS_WIN32);
    return S_OK;
}

Test.def:

LIBRARY "Test"
EXPORTS
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE
    DllRegisterServer   PRIVATE
    DllUnregisterServer   PRIVATE

Build and registration:

midl Test.idl
cl /LD Test.cpp Test.def oleaut32.lib
Test.reg
regsvr32 Test.dll

Test.reg:

REGEDIT
HKEY_CLASSES_ROOT\Test.CoTest\CLSID = {fc2ae506-88f3-4f00-9755-76768114617a}
HKEY_CLASSES_ROOT\Wow6432Node\Test.CoTest\CLSID = {fc2ae506-88f3-4f00-9755-76768114617a}
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{fc2ae506-88f3-4f00-9755-76768114617a} = Test.CoTest
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{fc2ae506-88f3-4f00-9755-76768114617a}\InProcServer32 = C:\Work\com\comg\Test.dll
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{fc2ae506-88f3-4f00-9755-76768114617a}\TypeLib = {cc9ec8d0-1aaa-4e32-97a3-9e875eb7d744}

Clients using header file:

TestC.c:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#define COBJMACROS
#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    ITest *pTest;
    int v;
    BSTR s1, s2, s3;
    CoInitialize(NULL);
    printf("COM/C:\n");
    res = CoCreateInstance(&CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, &IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    res = ITest_Add(pTest, 123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 345 = %d\n", v);
    s1 = SysAllocString(L"ABC");
    s2 = SysAllocString(L"XYZ");
    res = ITest_Concat(pTest, s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    ITest_Release(pTest);
    CoUninitialize();
    return 0;
}

Build and run:

cl TestC.c Test_i.c ole32.lib oleaut32.lib
TestC

What is happening:

  1. CoGetClassObject and CreateInstance / CoCreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  2. call QueryInterface to get ITest
  3. call methods on instance
  4. call Release on instance

Clients using type library:

TestCPP3.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CreateInstance:\n");
    try
    {
        ITestPtr spTest;
        res = spTest.CreateInstance(CLSID_CoTest);
        ReturnCheck(_T("CreateInstance"), res);
        long v = spTest->Add(123L, 456L);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        spTest = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP3.cpp ole32.lib oleaut32.lib
TestCPP3

TestCPP4.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with constructor:\n");
    try
    {
        ITestPtr spTest(__uuidof(CoTest));
        long v = spTest->Add(123, 456);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        spTest = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP4.cpp ole32.lib oleaut32.lib
TestCPP4

TestCS.cpp:

using System;

using Test;

public class MainClass
{
    public static void Main(string[] args)
    {
        Console.WriteLine("COM/C# static");
        CoTest cotest = new CoTestClass();
        ITest test = (ITest)cotest;
        Console.WriteLine("123 + 456 = {0}", test.Add(123, 456));
        Console.WriteLine("ABC + XYZ = {0}", test.Concat("ABC", "XYZ"));
    }
}

Build and run:

tlbimp /out=TestWrap.dll /namespace:Test Test.tlb
csc /platform:x86 /r:TestWrap.dll TestCS.cs
TestCS

TestPas.pas:

program TestPas;

{$mode delphi}{$H+}

uses
  ActiveX, TestLibrary_1_0_TLB;

var
  test : ITest;

begin
  writeln('COM/Pascal static:');
  CoInitialize(nil);
  test := CoCoTest.Create;
  writeln('123 + 456 = ', test.Add(123, 456));
  writeln('ABC + XYZ = ', test.Concat('ABC', 'XYZ'));
  CoUninitialize;
end.

Build and run:

importtl Test.tlb
fpc TestLibrary_1_0_TLB.pas
fpc TestPas.pas
TestPas

What is happening:

  1. import TLB
  2. pointer CreateInstance / pointer constructor / wrapper constructor
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  3. call QueryInterface to get ITest
  4. call methods on instance
  5. call Release on instance

Clients using scripting interface:

TestVBS.vbs:

WScript.Echo "COM/VBS:"
Set test = CreateObject("Test.CoTest")
WScript.Echo "123 + 456 = " & CStr(test.Add(CLng(123), CLng(456)))
WScript.Echo "ABC + XYZ = " & test.Concat("ABC", "XYZ")
Set test = Nothing

Build and run:

cscript TestVBS.vbs

TestCSDyn.cs:

using System;

public class MainClass
{
    public static void Main(string[] args)
    {
        Console.WriteLine("COM/C# dynamic");
        dynamic test = Activator.CreateInstance(Type.GetTypeFromProgID("Test.CoTest"));
        Console.WriteLine("123 + 456 = {0}", test.Add(123, 456));
        Console.WriteLine("ABC + XYZ = {0}", test.Concat("ABC", "XYZ"));
    }
}

Build and run:

csc /platform:x86 TestCSDyn.cs
TestCSDyn

TestPasDyn.pas:

program TestPasDyn;

uses
  ActiveX,ComObj;

var
  test : Variant;

begin
  writeln('COM/Pascal dynamic:');
  CoInitialize(nil);
  test := CreateOLEObject('Test.CoTest');
  writeln('123 + 456 = ', test.Add(123, 456));
  writeln('ABC + XYZ = ', test.Concat('ABC', 'XYZ'));
  CoUninitialize;
end.

Build and run:

fpc TestPasDyn.pas
TestPasDyn

TestJava.java:

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Variant;

public class TestJava {
    public static void main(String[] args) {
        System.out.println("COM/Java with Jacob ActiveXComponent");
        ActiveXComponent test = new ActiveXComponent("Test.CoTest");
        System.out.println("123 + 456 = " + test.invoke("Add", new Variant(123), new Variant(456)).getInt());
        System.out.println("ABC + XYZ = " + test.invoke("Concat", new Variant("ABC"), new Variant("XYZ")).getString());
    }
}

Build and run:

javac -cp %JACOBDIR%\jacob.jar TestJava.java
java -Djava.library.path=%JACOBDIR% -cp .;%JACOBDIR%\jacob.jar TestJava

Nobody will use IDispatch with C++, but it is still relevant to see the code to understand what is happening in other languages.

TestCPP5.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ via IDispatch:\n");
    CLSID clsid;
    res = CLSIDFromProgID(L"Test.CoTest", &CLSID);
    ReturnCheck(_T("CLSIDFromProgID"), res);
    IDispatch *pDisp = NULL;
    res = CoCreateInstance(clsid, NULL, CLSCTX_SERVER, IID_IDispatch, (void **)&pDisp);
    ReturnCheck(_T("CoCreateInstance"), res);
    DISPID dispid;
    LPOLESTR func;
    VARIANT args[2], retval;
    DISPPARAMS params;
    params.rgvarg = args;
    params.rgdispidNamedArgs = 0;
    params.cArgs = 2;
    params.cNamedArgs = 0;
    func = L"Add";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_I4;
    args[0].lVal = 456;
    VariantInit(&args[1]);
    args[1].vt = VT_I4;
    args[1].lVal = 123;
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("123 + 345 = %d\n", retval.lVal);
    func = L"Concat";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_BSTR;
    args[0].bstrVal = SysAllocString(L"ABC");
    VariantInit(&args[1]);
    args[1].vt = VT_BSTR;
    args[1].bstrVal = SysAllocString(L"XYZ");
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("3 ABC + 3 XYZ = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    pDisp->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP5.cpp ole32.lib oleaut32.lib
TestCPP5

What is happening:

  1. CreateObject / CreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance to get instance (calls AddRef on instance)
    5. call QueryInterface to get IDispatch
  2. call methods on instance
    1. use GetIDsOfNames to translate name to method
    2. use Invoke to call method
  3. set reference to null or let reference go out of scope
    1. call Release on instance

Iteration #8:

In this iteration we will:

Server:

Test.idl:

import "oaidl.idl";

// ITest
[object, uuid(A9296DFF-065A-4591-AE74-D4B0205F6F67), oleautomation, dual]
interface ITest : IDispatch
{
    [id(1)] HRESULT Add([in] long a, [in] long b, [out,retval] long *c);
    [id(2)] HRESULT Concat([in] BSTR a, [in] BSTR b, [out,retval]BSTR *c);
    [id(3),propget] HRESULT IV([out, retval] long *iv);
    [id(3),propput] HRESULT IV([in] long iv);
    [id(4),propget] HRESULT SV([out, retval] BSTR *sv);
    [id(4),propput] HRESULT SV([in] BSTR sv);
}

// Test library
[uuid(1A2EDF64-048D-43d0-BE7E-90C604592280), version(1.0)]
library TestLibrary
{
    importlib("stdole32.tlb");
    [uuid(575529A2-0360-407f-9133-4DCB664A61A1)]
    coclass CoTest
    {
        [default] interface ITest;
    };
};

Interface declare properties:

    [id(membernumber,propget)] HRESULT propertyname([out,retval] type *name) 
    [id(membernumber,propput)] HRESULT propertyname([in] type *_name)

TestEx.h:

#ifndef TESTEX_H
#define TESTEX_H

#include <windows.h>

#include "Test_i.c"
#include "Test.h"

class CoTest : public ITest
{
private:
    LONG volatile m_refcnt;
    long m_iv;
    BSTR m_sv;
public:
    CoTest();
    virtual ~CoTest();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // IDispatch
    STDMETHODIMP GetTypeInfoCount(UINT *pctinfo);
    STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo);
    STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
    STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExceptInfo, UINT *puArgErr);
    // ITest
    STDMETHODIMP Add(long a, long b, long *c);
    STDMETHODIMP Concat(BSTR a, BSTR b, BSTR *c);
    STDMETHODIMP get_IV(long *iv);
    STDMETHODIMP put_IV(long iv);
    STDMETHODIMP get_SV(BSTR *sv);
    STDMETHODIMP put_SV(BSTR sv);
};

class CoTestFactory : IClassFactory
{
private:
    LONG volatile m_refcnt;
public:
    CoTestFactory();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // IClassFactory
    STDMETHODIMP CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv);
    STDMETHODIMP LockServer(BOOL fLock);
};

#endif // TESTEX_H

Test.cpp:

#include <stdio.h>

#include <windows.h>
#include <oleauto.h>
#include <initguid.h>

#include "TestEx.h"

#define DISPID_ADD 1
#define DISPID_CONCAT 2
#define DISPID_IV 3
#define DISPID_SV 4

static LONG volatile g_lckcnt = 0;
static LONG volatile g_objcnt = 0;

CoTest::CoTest()
{
#ifdef DEBUG
    printf("CoTest ctor\n");
#endif
    m_iv = 0;
    m_sv = SysAllocString(L"");
    m_refcnt = 0;
    InterlockedIncrement(&g_objcnt);
}

CoTest::~CoTest()
{
#ifdef DEBUG
    printf("CoTest dtor\n");
#endif
    SysFreeString(m_sv);
    InterlockedDecrement(&g_objcnt);
}

STDMETHODIMP CoTest::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface IUnknown\n");
#endif
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_IDispatch)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface IDispatch\n");
#endif
        *ppv = (IDispatch*)this;
        ((ITest*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_ITest)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface ITest\n");
#endif
        *ppv = (ITest*)this;
        ((ITest*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CoTest::AddRef()
{
#ifdef DEBUG
    printf("CoTest AddRef\n");
#endif
    return InterlockedIncrement(&m_refcnt);
}

STDMETHODIMP_(ULONG) CoTest::Release()
{
#ifdef DEBUG
    printf("CoTest Release\n");
#endif
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

STDMETHODIMP CoTest::GetTypeInfoCount(UINT *pctinfo)
{
    *pctinfo = 0;
    return S_OK;
}

STDMETHODIMP CoTest::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
{
    *ppTInfo = NULL;
    return E_NOTIMPL;
}

STDMETHODIMP CoTest::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
    // max. 1 name per call
    if(cNames > 1) return E_INVALIDARG;
    if(_wcsicmp(rgszNames[0], L"Add") == 0)
    {
#ifdef DEBUG
        printf("CoTest GetIDsOfNames Add\n");
#endif
        *rgDispId = DISPID_ADD;
    }
    else if(_wcsicmp(rgszNames[0], L"Concat") == 0)
    {
#ifdef DEBUG
        printf("CoTest GetIDsOfNames Concat\n");
#endif
        *rgDispId = DISPID_CONCAT;
    }
    else if(_wcsicmp(rgszNames[0], L"IV") == 0)
    {
#ifdef DEBUG
        printf("CoTest GetIDsOfNames IV\n");
#endif
        *rgDispId = DISPID_IV;
    }
    else if(_wcsicmp(rgszNames[0], L"SV") == 0)
    {
#ifdef DEBUG
        printf("CoTest GetIDsOfNames SV\n");
#endif
        *rgDispId = DISPID_SV;
    }
    else
    {
        return DISP_E_UNKNOWNNAME;
    }
    return S_OK;
}

STDMETHODIMP CoTest::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExceptInfo, UINT *puArgErr)
{
    switch(dispIdMember)
    {
        case DISPID_ADD:
#ifdef DEBUG
            printf("CoTest Invoke Add\n");
#endif
            long ltmp;
            Add(pDispParams->rgvarg[1].lVal, pDispParams->rgvarg[0].lVal, <mp);
            pVarResult->vt = VT_I4;
            pVarResult->lVal = ltmp;
            return S_OK;
        case DISPID_CONCAT:
#ifdef DEBUG
            printf("CoTest Invoke Concat\n");
#endif
            BSTR bstrtmp;
            Concat(pDispParams->rgvarg[1].bstrVal, pDispParams->rgvarg[0].bstrVal, &bstrtmp);
            pVarResult->vt = VT_BSTR;
            pVarResult->bstrVal = bstrtmp;
            return S_OK;
        case DISPID_IV:
            if(wFlags & DISPATCH_PROPERTYGET)
            {
#ifdef DEBUG
                printf("CoTest Invoke Get IV\n");
#endif
                long ltmp;
                get_IV(<mp);
                pVarResult->vt = VT_I4;
                pVarResult->lVal = ltmp;
            }
            if(wFlags & DISPATCH_PROPERTYPUT)
            {
#ifdef DEBUG
                printf("CoTest Invoke Put IV\n");
#endif
                if(pDispParams->rgvarg[0].vt == VT_I4)
                {
                    put_IV(pDispParams->rgvarg[0].lVal);
                }
                else if(pDispParams->rgvarg[0].vt == VT_I2)
                {
                    put_IV(pDispParams->rgvarg[0].iVal);
                }
                else if(pDispParams->rgvarg[0].vt == VT_I1)
                {
                    put_IV(pDispParams->rgvarg[0].bVal);
                }
            }
            return S_OK;
        case DISPID_SV:
            if(wFlags & DISPATCH_PROPERTYGET)
            {
#ifdef DEBUG
                printf("CoTest Invoke Get SV\n");
#endif
                BSTR bstrtmp;
                get_SV(&bstrtmp);
                pVarResult->vt = VT_BSTR;
                pVarResult->bstrVal = bstrtmp;
            }
            if(wFlags & DISPATCH_PROPERTYPUT)
            {
#ifdef DEBUG
                printf("CoTest Invoke Put SV\n");
#endif
                put_SV(pDispParams->rgvarg[0].bstrVal);
            }
            return S_OK;
        default:
            return DISP_E_UNKNOWNINTERFACE;
    }
}

STDMETHODIMP CoTest::Add(long a, long b, long *c)
{
    *c = a + b;
    return S_OK;
}

STDMETHODIMP CoTest::Concat(BSTR a, BSTR b, BSTR *c)
{
    OLECHAR *buf = new OLECHAR[SysStringLen(a) + SysStringLen(b) + 1];
    wcscpy(buf, a);
    wcscat(buf, b);
    *c = SysAllocString(buf);
    delete[] buf;
    return S_OK;
}

STDMETHODIMP CoTest::get_IV(long *iv)
{
    *iv = m_iv;
    return S_OK;
}

STDMETHODIMP CoTest::put_IV(long iv)
{
    m_iv = iv;
    return S_OK;
}

STDMETHODIMP CoTest::get_SV(BSTR *sv)
{
    *sv = SysAllocString(m_sv);
    return S_OK;
}

STDMETHODIMP CoTest::put_SV(BSTR sv)
{
    SysReAllocString(&m_sv, sv);
    return S_OK;
}

CoTestFactory::CoTestFactory()
{
    m_refcnt = 0;
}

STDMETHODIMP CoTestFactory::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_IClassFactory)
    {
        *ppv = (IClassFactory*)this;
        ((IClassFactory*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CoTestFactory::AddRef()
{
    return InterlockedIncrement(&m_refcnt);
}

STDMETHODIMP_(ULONG) CoTestFactory::Release()
{
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

STDMETHODIMP CoTestFactory::CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("CoTestFactory CreateInstance\n");
#endif
    if(pUnk != NULL) return CLASS_E_NOAGGREGATION;
    CoTest *pTest = new CoTest();
    HRESULT res = pTest->QueryInterface(riid, ppv);
    if(res != S_OK) delete pTest;
    return res;
}

STDMETHODIMP CoTestFactory::LockServer(BOOL fLock)
{
    if(fLock) InterlockedIncrement(&g_lckcnt); else InterlockedDecrement(&g_lckcnt);
    return S_OK;
}

STDMETHODIMP DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("DllGetClassObject\n");
#endif
    if(rclsid == CLSID_CoTest)
    {
        CoTestFactory *pTestFact = new CoTestFactory();
        HRESULT res = pTestFact->QueryInterface(riid, ppv);
        if(res != S_OK) delete pTestFact;
        return res;
    }
    else
    {
        return CLASS_E_CLASSNOTAVAILABLE;
    }
}

STDMETHODIMP DllCanUnloadNow()
{
    if(g_lckcnt == 0 && g_objcnt == 0) return S_OK; else return S_FALSE;
}

STDMETHODIMP DllRegisterServer()
{
    ITypeLib *pTLib = NULL;
    LoadTypeLibEx(L"Test.tlb", REGKIND_REGISTER, &pTLib);
    pTLib->Release();
    return S_OK;
}

STDMETHODIMP DllUnregisterServer()
{
    UnRegisterTypeLib(LIBID_TestLibrary, 1, 0, LANG_NEUTRAL, SYS_WIN32);
    return S_OK;
}

Test.def:

LIBRARY "Test"
EXPORTS
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE
    DllRegisterServer   PRIVATE
    DllUnregisterServer   PRIVATE

Build and registration:

midl Test.idl
cl /LD Test.cpp Test.def oleaut32.lib
Test.reg
regsvr32 Test.dll

Test.reg:

REGEDIT
HKEY_CLASSES_ROOT\Test.CoTest\CLSID = {575529a2-0360-407f-9133-4dcb664a61a1}
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{575529a2-0360-407f-9133-4dcb664a61a1} = Test.CoTest
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{575529a2-0360-407f-9133-4dcb664a61a1}\InProcServer32 = C:\Work\com\comh\Test.dll
HKEY_CLASSES_ROOT\CLSID\Wow6432Node\{575529a2-0360-407f-9133-4dcb664a61a1}\TypeLib = {1a2edf64-048d-43d0-be7e-90c604592280}

Clients using header file:

TestC.c:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#define COBJMACROS
#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    ITest *pTest;
    int v, iv;
    BSTR s1, s2, s3, sv;
    CoInitialize(NULL);
    printf("COM/C:\n");
    res = CoCreateInstance(&CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, &IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    res = ITest_Add(pTest, 123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 345 = %d\n", v);
    s1 = SysAllocString(L"ABC");
    s2 = SysAllocString(L"XYZ");
    res = ITest_Concat(pTest, s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    ITest_get_IV(pTest, &iv);
    ITest_get_SV(pTest, &sv);
    printf("IV = %d SV = %d %ls\n", iv, SysStringLen(sv), sv);
    SysFreeString(sv);
    ITest_put_IV(pTest, 123);
    ITest_put_SV(pTest, SysAllocString(L"ABC"));
    ITest_get_IV(pTest, &iv);
    ITest_get_SV(pTest, &sv);
    printf("IV = %d SV = %d %ls\n", iv, SysStringLen(sv), sv);
    SysFreeString(sv);
    ITest_Release(pTest);
    CoUninitialize();
    return 0;
}

Build and run:

cl TestC.c Test_i.c ole32.lib oleaut32.lib
TestC

What is happening:

  1. CoGetClassObject and CreateInstance / CoCreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  2. call QueryInterface to get ITest
  3. call methods on instance
  4. call Release on instance

Clients using type library:

TestCPP3.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CreateInstance:\n");
    try
    {
        ITestPtr spTest;
        res = spTest.CreateInstance(CLSID_CoTest);
        ReturnCheck(_T("CreateInstance"), res);
        long v = spTest->Add(123L, 456L);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        BSTR sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest->PutIV(123);
        spTest->PutSV(SysAllocString(L"ABC"));
        sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP3.cpp ole32.lib oleaut32.lib
TestCPP3

TestCPP4.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with constructor:\n");
    try
    {
        ITestPtr spTest(__uuidof(CoTest));
        long v = spTest->Add(123, 456);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        BSTR sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest->PutIV(123);
        spTest->PutSV(SysAllocString(L"ABC"));
        sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP4.cpp ole32.lib oleaut32.lib
TestCPP4

TestCS.cpp:

using System;

using Test;

public class MainClass
{
    public static void Main(string[] args)
    {
        Console.WriteLine("COM/C# static");
        CoTest cotest = new CoTestClass();
        ITest test = (ITest)cotest;
        Console.WriteLine("123 + 456 = {0}", test.Add(123, 456));
        Console.WriteLine("ABC + XYZ = {0}", test.Concat("ABC", "XYZ"));
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
        test.IV = 123;
        test.SV = "ABC";
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
    }
}

Build and run:

tlbimp /out=TestWrap.dll /namespace:Test Test.tlb
csc /platform:x86 /r:TestWrap.dll TestCS.cs
TestCS

TestPas.pas:

program TestPas;

{$mode delphi}{$H+}

uses
  ActiveX, TestLibrary_1_0_TLB;

var
  test : ITest;

begin
  writeln('COM/Pascal static:');
  CoInitialize(nil);
  test := CoCoTest.Create;
  writeln('123 + 456 = ', test.Add(123, 456));
  writeln('ABC + XYZ = ', test.Concat('ABC', 'XYZ'));
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  test.IV := 123;
  test.SV := 'ABC';
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  CoUninitialize;
end.

Build and run:

importtl Test.tlb
fpc TestLibrary_1_0_TLB.pas
fpc TestPas.pas
TestPas

What is happening:

  1. import TLB
  2. pointer CreateInstance / pointer constructor / wrapper constructor
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  3. call QueryInterface to get ITest
  4. call methods on instance
  5. call Release on instance

Clients using scripting interface:

TestVBS.vbs:

WScript.Echo "COM/VBS:"
Set test = CreateObject("Test.CoTest")
WScript.Echo "123 + 456 = " & CStr(test.Add(CLng(123), CLng(456)))
WScript.Echo "ABC + XYZ = " & test.Concat("ABC", "XYZ")
WScript.Echo "IV = " & CStr(test.IV) & " SV = " & test.SV
test.IV = 123
test.SV = "ABC"
WScript.Echo "IV = " & CStr(test.IV) & " SV = " & test.SV
Set test = Nothing

Build and run:

cscript TestVBS.vbs

TestCSDyn.cs:

using System;

public class MainClass
{
    public static void Main(string[] args)
    {
        Console.WriteLine("COM/C# dynamic");
        dynamic test = Activator.CreateInstance(Type.GetTypeFromProgID("Test.CoTest"));
        Console.WriteLine("123 + 456 = {0}", test.Add(123, 456));
        Console.WriteLine("ABC + XYZ = {0}", test.Concat("ABC", "XYZ"));
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
        test.IV = 123;
        test.SV = "ABC";
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
    }
}

Build and run:

csc /platform:x86 TestCSDyn.cs
TestCSDyn

TestPasDyn.pas:

program TestPasDyn;

uses
  ActiveX,ComObj;

var
  test : Variant;

begin
  writeln('COM/Pascal dynamic:');
  CoInitialize(nil);
  test := CreateOLEObject('Test.CoTest');
  writeln('123 + 456 = ', test.Add(123, 456));
  writeln('ABC + XYZ = ', test.Concat('ABC', 'XYZ'));
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  test.IV := 123;
  test.SV := 'ABC';
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  CoUninitialize;
end.

Build and run:

fpc TestPasDyn.pas
TestPasDyn

TestJava.java:

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Variant;

public class TestJava {
    public static void main(String[] args) {
        System.out.println("COM/Java with Jacob ActiveXComponent");
        ActiveXComponent test = new ActiveXComponent("Test.CoTest");
        System.out.println("123 + 456 = " + test.invoke("Add", new Variant(123), new Variant(456)).getInt());
        System.out.println("ABC + XYZ = " + test.invoke("Concat", new Variant("ABC"), new Variant("XYZ")).getString());
        System.out.println("IV = " + test.getPropertyAsInt("IV") + " SV = " + test.getPropertyAsString("SV"));
        test.setProperty("IV", 123);
        test.setProperty("SV", "ABC");
        System.out.println("IV = " + test.getPropertyAsInt("IV") + " SV = " + test.getPropertyAsString("SV"));
    }
}

Build and run:

javac -cp %JACOBDIR%\jacob.jar TestJava.java
java -Djava.library.path=%JACOBDIR% -cp .;%JACOBDIR%\jacob.jar TestJava

Nobody will use IDispatch with C++, but it is still relevant to see the code to understand what is happening in other languages.

TestCPP5.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ via IDispatch:\n");
    CLSID clsid;
    res = CLSIDFromProgID(L"Test.CoTest", &CLSid);
    ReturnCheck(_T("CLSIDFromProgID"), res);
    IDispatch *pDisp = NULL;
    res = CoCreateInstance(clsid, NULL, CLSCTX_SERVER, IID_IDispatch, (void **)&pDisp);
    ReturnCheck(_T("CoCreateInstance"), res);
    DISPID dispid;
    LPOLESTR func;
    VARIANT args[2], retval;
    DISPPARAMS params;
    params.rgvarg = args;
    params.rgdispidNamedArgs = 0;
    params.cNamedArgs = 0;
    // Add
    params.cArgs = 2;
    func = L"Add";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_I4;
    args[0].lVal = 456;
    VariantInit(&args[1]);
    args[1].vt = VT_I4;
    args[1].lVal = 123;
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("123 + 345 = %d\n", retval.lVal);
    // Concat
    params.cArgs = 2;
    func = L"Concat";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_BSTR;
    args[0].bstrVal = SysAllocString(L"ABC");
    VariantInit(&args[1]);
    args[1].vt = VT_BSTR;
    args[1].bstrVal = SysAllocString(L"XYZ");
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("3 ABC + 3 XYZ = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    // Get
    params.cArgs = 0;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("IV = %d", retval.lVal);
    params.cArgs = 0;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf(" SV = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    // Put
    params.cArgs = 1;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_I4;
    args[0].lVal = 123;
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    params.cArgs = 1;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_BSTR;
    args[0].bstrVal = SysAllocString(L"ABC");
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    // Get
    params.cArgs = 0;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("IV = %d", retval.lVal);
    params.cArgs = 0;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf(" SV = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    //
    pDisp->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP5.cpp ole32.lib oleaut32.lib
TestCPP5

What is happening:

  1. CreateObject / CreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance to get instance (calls AddRef on instance)
    5. call QueryInterface to get IDispatch
  2. call methods on instance
    1. use GetIDsOfNames to translate name to method
    2. use Invoke to call method
  3. set reference to null or let reference go out of scope
    1. call Release on instance

Iteration #9:

In this iteration we will:

Server:

Test.idl:

import "oaidl.idl";

// ITest
[object, uuid(45BE0445-DA8F-44f2-A44F-B7F041127F3A), oleautomation, dual]
interface ITest : IDispatch
{
    [id(1)] HRESULT Add([in] long a, [in] long b, [out,retval] long *c);
    [id(2)] HRESULT Concat([in] BSTR a, [in] BSTR b, [out,retval]BSTR *c);
    [id(3),propget] HRESULT IV([out, retval] long *iv);
    [id(3),propput] HRESULT IV([in] long iv);
    [id(4),propget] HRESULT SV([out, retval] BSTR *sv);
    [id(4),propput] HRESULT SV([in] BSTR sv);
}

// Test library
[uuid(1C187D8E-FBA1-432a-B0C3-E0506642E789), version(1.0)]
library TestLibrary
{
    importlib("stdole32.tlb");
    [uuid(39599C7C-2D46-4217-904C-EA10C726D59B)]
    coclass CoTest
    {
        [default] interface ITest;
    };
};

TestEx.h:

#ifndef TESTEX_H
#define TESTEX_H

#include <windows.h>

#include "Test_i.c"
#include "Test.h"

class CoTest : public ITest
{
private:
    LONG volatile m_refcnt;
    ITypeInfo *m_ptypeInfo;
    long m_iv;
    BSTR m_sv;
public:
    CoTest();
    virtual ~CoTest();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // IDispatch
    STDMETHODIMP GetTypeInfoCount(UINT *pctinfo);
    STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo);
    STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
    STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExceptInfo, UINT *puArgErr);
    // ITest
    STDMETHODIMP Add(long a, long b, long *c);
    STDMETHODIMP Concat(BSTR a, BSTR b, BSTR *c);
    STDMETHODIMP get_IV(long *iv);
    STDMETHODIMP put_IV(long iv);
    STDMETHODIMP get_SV(BSTR *sv);
    STDMETHODIMP put_SV(BSTR sv);
};

class CoTestFactory : IClassFactory
{
private:
    LONG volatile m_refcnt;
public:
    CoTestFactory();
    // IUnknown
    STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    // IClassFactory
    STDMETHODIMP CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv);
    STDMETHODIMP LockServer(BOOL fLock);
};

#endif // TESTEX_H

Test.cpp:

#include <stdio.h>

#include <windows.h>
#include <oleauto.h>
#include <initguid.h>

#include "TestEx.h"

static LONG volatile g_lckcnt = 0;
static LONG volatile g_objcnt = 0;

CoTest::CoTest() : m_ptypeInfo(NULL)
{
#ifdef DEBUG
    printf("CoTest ctor\n");
#endif
    m_iv = 0;
    m_sv = SysAllocString(L"");
    m_refcnt = 0;
    InterlockedIncrement(&g_objcnt);
    ITypeLib *pTypeLibrary = NULL;
    HRESULT hr = LoadRegTypeLib(LIBID_TestLibrary, 1, 0, LANG_NEUTRAL, &pTypeLibrary);
    if(SUCCEEDED(hr))
    {
        pTypeLibrary->GetTypeInfoOfGuid(IID_ITest, &m_ptypeInfo);
        pTypeLibrary->Release();
    }
}

CoTest::~CoTest()
{
#ifdef DEBUG
    printf("CoTest dtor\n");
#endif
    SysFreeString(m_sv);
    m_ptypeInfo->Release();
    InterlockedDecrement(&g_objcnt);
}

STDMETHODIMP CoTest::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface IUnknown\n");
#endif
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_IDispatch)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface IDispatch\n");
#endif
        *ppv = (IDispatch*)this;
        ((ITest*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_ITest)
    {
#ifdef DEBUG
        printf("CoTest QueryInterface ITest\n");
#endif
        *ppv = (ITest*)this;
        ((ITest*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CoTest::AddRef()
{
#ifdef DEBUG
    printf("CoTest AddRef\n");
#endif
    return InterlockedIncrement(&m_refcnt);
}

STDMETHODIMP_(ULONG) CoTest::Release()
{
#ifdef DEBUG
    printf("CoTest Release\n");
#endif
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

STDMETHODIMP CoTest::GetTypeInfoCount(UINT *pctinfo)
{
    *pctinfo = 1;
    return S_OK;
}

STDMETHODIMP CoTest::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
{
    *ppTInfo = m_ptypeInfo;
    m_ptypeInfo->AddRef();
    return S_OK;
}

STDMETHODIMP CoTest::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId)
{
#ifdef DEBUG
    printf("CoTest GetIDsOfNames %ls\n", rgszNames[0]);
#endif
    return DispGetIDsOfNames(m_ptypeInfo, rgszNames, cNames, rgDispId);
}

STDMETHODIMP CoTest::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExceptInfo, UINT *puArgErr)
{
#ifdef DEBUG
    printf("CoTest Invoke\n");
#endif
    return DispInvoke(this, m_ptypeInfo, dispIdMember, wFlags, pDispParams, pVarResult, pExceptInfo, puArgErr);
}

STDMETHODIMP CoTest::Add(long a, long b, long *c)
{
    *c = a + b;
    return S_OK;
}

STDMETHODIMP CoTest::Concat(BSTR a, BSTR b, BSTR *c)
{
    OLECHAR *buf = new OLECHAR[SysStringLen(a) + SysStringLen(b) + 1];
    wcscpy(buf, a);
    wcscat(buf, b);
    *c = SysAllocString(buf);
    delete[] buf;
    return S_OK;
}

STDMETHODIMP CoTest::get_IV(long *iv)
{
    *iv = m_iv;
    return S_OK;
}

STDMETHODIMP CoTest::put_IV(long iv)
{
    m_iv = iv;
    return S_OK;
}

STDMETHODIMP CoTest::get_SV(BSTR *sv)
{
    *sv = SysAllocString(m_sv);
    return S_OK;
}

STDMETHODIMP CoTest::put_SV(BSTR sv)
{
    SysReAllocString(&m_sv, sv);
    return S_OK;
}

CoTestFactory::CoTestFactory()
{
    m_refcnt = 0;
}

STDMETHODIMP CoTestFactory::QueryInterface(REFIID riid, void **ppv)
{
    if(riid == IID_IUnknown)
    {
        *ppv = (IUnknown*)this;
        ((IUnknown*)(*ppv))->AddRef();
        return S_OK;
    }
    else if(riid == IID_IClassFactory)
    {
        *ppv = (IClassFactory*)this;
        ((IClassFactory*)(*ppv))->AddRef();
        return S_OK;
    }
    else
    {
        *ppv = NULL;
        return E_NOINTERFACE;
    }
}

STDMETHODIMP_(ULONG) CoTestFactory::AddRef()
{
    return InterlockedIncrement(&m_refcnt);
}

STDMETHODIMP_(ULONG) CoTestFactory::Release()
{
    InterlockedDecrement(&m_refcnt);
    if(m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    else
    {
        return m_refcnt;
    }
}

STDMETHODIMP CoTestFactory::CreateInstance(LPUNKNOWN pUnk, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("CoTestFactory CreateInstance\n");
#endif
    if(pUnk != NULL) return CLASS_E_NOAGGREGATION;
    CoTest *pTest = new CoTest();
    HRESULT res = pTest->QueryInterface(riid, ppv);
    if(res != S_OK) delete pTest;
    return res;
}

STDMETHODIMP CoTestFactory::LockServer(BOOL fLock)
{
    if(fLock) InterlockedIncrement(&g_lckcnt); else InterlockedDecrement(&g_lckcnt);
    return S_OK;
}

STDMETHODIMP DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("DllGetClassObject\n");
#endif
    if(rclsid == CLSID_CoTest)
    {
        CoTestFactory *pTestFact = new CoTestFactory();
        HRESULT res = pTestFact->QueryInterface(riid, ppv);
        if(res != S_OK) delete pTestFact;
        return res;
    }
    else
    {
        return CLASS_E_CLASSNOTAVAILABLE;
    }
}

STDMETHODIMP DllCanUnloadNow()
{
    if(g_lckcnt == 0 && g_objcnt == 0) return S_OK; else return S_FALSE;
}

STDMETHODIMP DllRegisterServer()
{
    ITypeLib *pTLib = NULL;
    LoadTypeLibEx(L"Test.tlb", REGKIND_REGISTER, &pTLib);
    pTLib->Release();
    return S_OK;
}

STDMETHODIMP DllUnregisterServer()
{
    UnRegisterTypeLib(LIBID_TestLibrary, 1, 0, LANG_NEUTRAL, SYS_WIN32);
    return S_OK;
}

Test.def:

LIBRARY "Test"
EXPORTS
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE
    DllRegisterServer   PRIVATE
    DllUnregisterServer   PRIVATE

Build and registration:

midl Test.idl
cl /LD Test.cpp Test.def oleaut32.lib
Test.reg
regsvr32 Test.dll

Test.reg:

REGEDIT
HKEY_CLASSES_ROOT\Test.CoTest\CLSID = {39599c7c-2d46-4217-904c-ea10c726d59b}
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{39599c7c-2d46-4217-904c-ea10c726d59b} = Test.CoTest
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{39599c7c-2d46-4217-904c-ea10c726d59b}\InProcServer32 = C:\Work\com\comi\Test.dll
HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{39599c7c-2d46-4217-904c-ea10c726d59b}\TypeLib = {1c187d8e-fba1-432a-b0c3-e0506642e789}

Clients using header file:

TestC.c:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#define COBJMACROS
#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    ITest *pTest;
    int v, iv;
    BSTR s1, s2, s3, sv;
    CoInitialize(NULL);
    printf("COM/C:\n");
    res = CoCreateInstance(&CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, &IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    res = ITest_Add(pTest, 123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 345 = %d\n", v);
    s1 = SysAllocString(L"ABC");
    s2 = SysAllocString(L"XYZ");
    res = ITest_Concat(pTest, s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    ITest_get_IV(pTest, &iv);
    ITest_get_SV(pTest, &sv);
    printf("IV = %d SV = %d %ls\n", iv, SysStringLen(sv), sv);
    SysFreeString(sv);
    ITest_put_IV(pTest, 123);
    ITest_put_SV(pTest, SysAllocString(L"ABC"));
    ITest_get_IV(pTest, &iv);
    ITest_get_SV(pTest, &sv);
    printf("IV = %d SV = %d %ls\n", iv, SysStringLen(sv), sv);
    SysFreeString(sv);
    ITest_Release(pTest);
    CoUninitialize();
    return 0;
}

Build and run:

cl TestC.c Test_i.c ole32.lib oleaut32.lib
TestC

What is happening:

  1. CoGetClassObject and CreateInstance / CoCreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  2. call QueryInterface to get ITest
  3. call methods on instance
  4. call Release on instance

Clients using type library:

TestCPP3.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CreateInstance:\n");
    try
    {
        ITestPtr spTest;
        res = spTest.CreateInstance(CLSID_CoTest);
        ReturnCheck(_T("CreateInstance"), res);
        long v = spTest->Add(123L, 456L);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        BSTR sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest->PutIV(123);
        spTest->PutSV(SysAllocString(L"ABC"));
        sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP3.cpp ole32.lib oleaut32.lib
TestCPP3

TestCPP4.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with constructor:\n");
    try
    {
        ITestPtr spTest(__uuidof(CoTest));
        long v = spTest->Add(123, 456);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        BSTR sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest->PutIV(123);
        spTest->PutSV(SysAllocString(L"ABC"));
        sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP4.cpp ole32.lib oleaut32.lib
TestCPP4

TestCS.cpp:

using System;

using Test;

public class MainClass
{
    public static void Main(string[] args)
    {
        Console.WriteLine("COM/C# static");
        CoTest cotest = new CoTestClass();
        ITest test = (ITest)cotest;
        Console.WriteLine("123 + 456 = {0}", test.Add(123, 456));
        Console.WriteLine("ABC + XYZ = {0}", test.Concat("ABC", "XYZ"));
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
        test.IV = 123;
        test.SV = "ABC";
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
    }
}

Build and run:

tlbimp /out=TestWrap.dll /namespace:Test Test.tlb
csc /platform:x86 /r:TestWrap.dll TestCS.cs
TestCS

TestPas.pas:

program TestPas;

{$mode delphi}{$H+}

uses
  ActiveX, TestLibrary_1_0_TLB;

var
  test : ITest;

begin
  writeln('COM/Pascal static:');
  CoInitialize(nil);
  test := CoCoTest.Create;
  writeln('123 + 456 = ', test.Add(123, 456));
  writeln('ABC + XYZ = ', test.Concat('ABC', 'XYZ'));
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  test.IV := 123;
  test.SV := 'ABC';
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  CoUninitialize;
end.

Build and run:

importtl Test.tlb
fpc TestLibrary_1_0_TLB.pas
fpc TestPas.pas
TestPas

What is happening:

  1. import TLB
  2. pointer CreateInstance / pointer constructor / wrapper constructor
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  3. call QueryInterface to get ITest
  4. call methods on instance
  5. call Release on instance

Clients using scripting interface:

TestVBS.vbs:

WScript.Echo "COM/VBS:"
Set test = CreateObject("Test.CoTest")
WScript.Echo "123 + 456 = " & CStr(test.Add(CLng(123), CLng(456)))
WScript.Echo "ABC + XYZ = " & test.Concat("ABC", "XYZ")
WScript.Echo "IV = " & CStr(test.IV) & " SV = " & test.SV
test.IV = 123
test.SV = "ABC"
WScript.Echo "IV = " & CStr(test.IV) & " SV = " & test.SV
Set test = Nothing

Build and run:

cscript TestVBS.vbs

TestCSDyn.cs:

using System;

public class MainClass
{
    public static void Main(string[] args)
    {
        Console.WriteLine("COM/C# dynamic");
        dynamic test = Activator.CreateInstance(Type.GetTypeFromProgID("Test.CoTest"));
        Console.WriteLine("123 + 456 = {0}", test.Add(123, 456));
        Console.WriteLine("ABC + XYZ = {0}", test.Concat("ABC", "XYZ"));
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
        test.IV = 123;
        test.SV = "ABC";
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
    }
}

Build and run:

csc /platform:x86 TestCSDyn.cs
TestCSDyn

TestPasDyn.pas:

program TestPasDyn;

uses
  ActiveX,ComObj;

var
  test : Variant;

begin
  writeln('COM/Pascal dynamic:');
  CoInitialize(nil);
  test := CreateOLEObject('Test.CoTest');
  writeln('123 + 456 = ', test.Add(123, 456));
  writeln('ABC + XYZ = ', test.Concat('ABC', 'XYZ'));
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  test.IV := 123;
  test.SV := 'ABC';
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  CoUninitialize;
end.

Build and run:

fpc TestPasDyn.pas
TestPasDyn

TestJava.java:

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Variant;

public class TestJava {
    public static void main(String[] args) {
        System.out.println("COM/Java with Jacob ActiveXComponent");
        ActiveXComponent test = new ActiveXComponent("Test.CoTest");
        System.out.println("123 + 456 = " + test.invoke("Add", new Variant(123), new Variant(456)).getInt());
        System.out.println("ABC + XYZ = " + test.invoke("Concat", new Variant("ABC"), new Variant("XYZ")).getString());
        System.out.println("IV = " + test.getPropertyAsInt("IV") + " SV = " + test.getPropertyAsString("SV"));
        test.setProperty("IV", 123);
        test.setProperty("SV", "ABC");
        System.out.println("IV = " + test.getPropertyAsInt("IV") + " SV = " + test.getPropertyAsString("SV"));
    }
}

Build and run:

javac -cp %JACOBDIR%\jacob.jar TestJava.java
java -Djava.library.path=%JACOBDIR% -cp .;%JACOBDIR%\jacob.jar TestJava

Nobody will use IDispatch with C++, but it is still relevant to see the code to understand what is happening in other languages.

TestCPP5.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ via IDispatch:\n");
    CLSID clsid;
    res = CLSIDFromProgID(L"Test.CoTest", &CLSid);
    ReturnCheck(_T("CLSIDFromProgID"), res);
    IDispatch *pDisp = NULL;
    res = CoCreateInstance(clsid, NULL, CLSCTX_SERVER, IID_IDispatch, (void **)&pDisp);
    ReturnCheck(_T("CoCreateInstance"), res);
    DISPID dispid;
    LPOLESTR func;
    VARIANT args[2], retval;
    DISPPARAMS params;
    params.rgvarg = args;
    params.rgdispidNamedArgs = 0;
    params.cNamedArgs = 0;
    // Add
    params.cArgs = 2;
    func = L"Add";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_I4;
    args[0].lVal = 456;
    VariantInit(&args[1]);
    args[1].vt = VT_I4;
    args[1].lVal = 123;
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("123 + 345 = %d\n", retval.lVal);
    // Concat
    params.cArgs = 2;
    func = L"Concat";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_BSTR;
    args[0].bstrVal = SysAllocString(L"ABC");
    VariantInit(&args[1]);
    args[1].vt = VT_BSTR;
    args[1].bstrVal = SysAllocString(L"XYZ");
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("3 ABC + 3 XYZ = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    // Get
    params.cArgs = 0;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("IV = %d", retval.lVal);
    params.cArgs = 0;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf(" SV = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    // Put
    DISPID temp = DISPID_PROPERTYPUT; // these 3 lines are necessary
    params.rgdispidNamedArgs = &temp;
    params.cNamedArgs = 1;
    //
    params.cArgs = 1;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_I4;
    args[0].lVal = 123;
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &params, NULL, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    params.cArgs = 1;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_BSTR;
    args[0].bstrVal = SysAllocString(L"ABC");
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &params, NULL, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    //
    params.rgdispidNamedArgs = 0;
    params.cNamedArgs = 0;
    // Get
    params.cArgs = 0;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("IV = %d", retval.lVal);
    params.cArgs = 0;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf(" SV = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    //
    pDisp->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP5.cpp ole32.lib oleaut32.lib
TestCPP5

What is happening:

  1. CreateObject / CreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance to get instance (calls AddRef on instance)
    5. call QueryInterface to get IDispatch
  2. call methods on instance
    1. use GetIDsOfNames to translate name to method
    2. use Invoke to call method
  3. set reference to null or let reference go out of scope
    1. call Release on instance

Iteration #10:

In this iteration we will:

Note that this is how one should write a COM component as it requires significant less code than the previous ways. But the code is also somewhat hard to understand and the previous iterations should help understand what this code is doing.

Server:

Test.idl:

import "oaidl.idl";

// ITest
[object, uuid(1B255E86-9F4C-442c-8987-947BD9D6AC7C), oleautomation, dual, pointer_default(unique)]
interface ITest : IDispatch
{
    [id(1)] HRESULT Add([in] long a, [in] long b, [out,retval] long *c);
    [id(2)] HRESULT Concat([in] BSTR a, [in] BSTR b, [out,retval]BSTR *c);
    [id(3),propget] HRESULT IV([out, retval] long *iv);
    [id(3),propput] HRESULT IV([in] long iv);
    [id(4),propget] HRESULT SV([out, retval] BSTR *sv);
    [id(4),propput] HRESULT SV([in] BSTR sv);
}

// Test library
[uuid(105A7B5F-67E9-4d10-A22D-B3C989904332), version(1.0)]
library TestLibrary
{
    importlib("stdole32.tlb");
    [uuid(A178C19F-C64B-4a96-8D6C-83698E5B7A70)]
    coclass CoTest
    {
        [default] interface ITest;
    };
};

TestEx.h:

#ifndef TESTEX_H
#define TESTEX_H

#include <windows.h>
#include <tchar.h>

#include <atlbase.h>
#include <atlcom.h>

#include "Test_i.c"
#include "Test.h"

class ATL_NO_VTABLE CCoTest : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCoTest, &CLSID_CoTest>, public IDispatchImpl<ITest, &IID_ITest, &LIBID_TestLibrary>
{
private:
    long m_iv;
    BSTR m_sv;
public:
    CCoTest();
    virtual ~CCoTest();
    BEGIN_COM_MAP(CCoTest)
       COM_INTERFACE_ENTRY(ITest)
       COM_INTERFACE_ENTRY2(IDispatch, ITest)
    END_COM_MAP()
    // ITest
    STDMETHODIMP Add(long a, long b, long *c);
    STDMETHODIMP Concat(BSTR a, BSTR b, BSTR *c);
    STDMETHODIMP get_IV(long *iv);
    STDMETHODIMP put_IV(long iv);
    STDMETHODIMP get_SV(BSTR *sv);
    STDMETHODIMP put_SV(BSTR sv);
    static HRESULT WINAPI UpdateRegistry(BOOL b)
    {
        return _Module.UpdateRegistryClass(CLSID_CoTest, _T("Test.CoTest.1"), _T("Test.CoTest"), 0U, THREADFLAGS_APARTMENT, b);
    }
};

#endif // TESTEX_H

Test.cpp:

#include <stdio.h>

#include <windows.h>
#include <oleauto.h>
#include <initguid.h>

#include <atlbase.h>
extern CComModule _Module;
#include <atlcom.h>

#include "TestEx.h"

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
    OBJECT_ENTRY(CLSID_CoTest, CCoTest)
END_OBJECT_MAP()

CCoTest::CCoTest()
{
#ifdef DEBUG
    printf("CCoTest ctor\n");
#endif
    m_iv = 0;
    m_sv = SysAllocString(L"");
}

CCoTest::~CCoTest()
{
#ifdef DEBUG
    printf("CCoTest dtor\n");
#endif
    SysFreeString(m_sv);
}

STDMETHODIMP CCoTest::Add(long a, long b, long *c)
{
    *c = a + b;
    return S_OK;
}

STDMETHODIMP CCoTest::Concat(BSTR a, BSTR b, BSTR *c)
{
    OLECHAR *buf = new OLECHAR[SysStringLen(a) + SysStringLen(b) + 1];
    wcscpy(buf, a);
    wcscat(buf, b);
    *c = SysAllocString(buf);
    delete[] buf;
    return S_OK;
}

STDMETHODIMP CCoTest::get_IV(long *iv)
{
    *iv = m_iv;
    return S_OK;
}

STDMETHODIMP CCoTest::put_IV(long iv)
{
    m_iv = iv;
    return S_OK;
}

STDMETHODIMP CCoTest::get_SV(BSTR *sv)
{
    *sv = SysAllocString(m_sv);
    return S_OK;
}

STDMETHODIMP CCoTest::put_SV(BSTR sv)
{
    SysReAllocString(&m_sv, sv);
    return S_OK;
}

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
    if(dwReason == DLL_PROCESS_ATTACH)
    {
        _Module.Init(ObjectMap, hInstance, &LIBID_TestLibrary);
    }
    else if(dwReason == DLL_PROCESS_DETACH)
    {
        _Module.Term();
    }
    return TRUE;
}

STDMETHODIMP DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("DllGetClassObject\n");
#endif
    return _Module.GetClassObject(rclsid, riid, ppv);
}

STDMETHODIMP DllCanUnloadNow()
{
    return (_Module.GetLockCount() == 0) ? S_OK : S_FALSE;
}

STDMETHODIMP DllRegisterServer()
{
    return _Module.RegisterServer(TRUE);
}

STDMETHODIMP DllUnregisterServer()
{
    return _Module.UnregisterServer(TRUE);
}

Test.def:

LIBRARY "Test"
EXPORTS
    DllMain             PRIVATE
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE
    DllRegisterServer   PRIVATE
    DllUnregisterServer PRIVATE

Build and registration:

midl Test.idl
cl /LD Test.cpp Test.def oleaut32.lib
regsvr32 Test.dll

Clients using header file:

TestC.c:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#define COBJMACROS
#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    ITest *pTest;
    int v, iv;
    BSTR s1, s2, s3, sv;
    CoInitialize(NULL);
    printf("COM/C:\n");
    res = CoCreateInstance(&CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, &IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    res = ITest_Add(pTest, 123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 345 = %d\n", v);
    s1 = SysAllocString(L"ABC");
    s2 = SysAllocString(L"XYZ");
    res = ITest_Concat(pTest, s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    ITest_get_IV(pTest, &iv);
    ITest_get_SV(pTest, &sv);
    printf("IV = %d SV = %d %ls\n", iv, SysStringLen(sv), sv);
    SysFreeString(sv);
    ITest_put_IV(pTest, 123);
    ITest_put_SV(pTest, SysAllocString(L"ABC"));
    ITest_get_IV(pTest, &iv);
    ITest_get_SV(pTest, &sv);
    printf("IV = %d SV = %d %ls\n", iv, SysStringLen(sv), sv);
    SysFreeString(sv);
    ITest_Release(pTest);
    CoUninitialize();
    return 0;
}

Build and run:

cl TestC.c Test_i.c ole32.lib oleaut32.lib
TestC

What is happening:

  1. CoGetClassObject and CreateInstance / CoCreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  2. call QueryInterface to get ITest
  3. call methods on instance
  4. call Release on instance

Clients using type library:

TestCPP3.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CreateInstance:\n");
    try
    {
        ITestPtr spTest;
        res = spTest.CreateInstance(CLSID_CoTest);
        ReturnCheck(_T("CreateInstance"), res);
        long v = spTest->Add(123L, 456L);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        BSTR sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest->PutIV(123);
        spTest->PutSV(SysAllocString(L"ABC"));
        sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP3.cpp ole32.lib oleaut32.lib
TestCPP3

TestCPP4.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with constructor:\n");
    try
    {
        ITestPtr spTest(__uuidof(CoTest));
        long v = spTest->Add(123, 456);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        BSTR sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest->PutIV(123);
        spTest->PutSV(SysAllocString(L"ABC"));
        sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP4.cpp ole32.lib oleaut32.lib
TestCPP4

TestCS.cpp:

using System;

using Test;

public class MainClass
{
    public static void Main(string[] args)
    {
        Console.WriteLine("COM/C# static");
        CoTest cotest = new CoTestClass();
        ITest test = (ITest)cotest;
        Console.WriteLine("123 + 456 = {0}", test.Add(123, 456));
        Console.WriteLine("ABC + XYZ = {0}", test.Concat("ABC", "XYZ"));
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
        test.IV = 123;
        test.SV = "ABC";
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
    }
}

Build and run:

tlbimp /out=TestWrap.dll /namespace:Test Test.tlb
csc /platform:x86 /r:TestWrap.dll TestCS.cs
TestCS

TestPas.pas:

program TestPas;

{$mode delphi}{$H+}

uses
  ActiveX, TestLibrary_1_0_TLB;

var
  test : ITest;

begin
  writeln('COM/Pascal static:');
  CoInitialize(nil);
  test := CoCoTest.Create;
  writeln('123 + 456 = ', test.Add(123, 456));
  writeln('ABC + XYZ = ', test.Concat('ABC', 'XYZ'));
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  test.IV := 123;
  test.SV := 'ABC';
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  CoUninitialize;
end.

Build and run:

importtl Test.tlb
fpc TestLibrary_1_0_TLB.pas
fpc TestPas.pas
TestPas

What is happening:

  1. import TLB
  2. pointer CreateInstance / pointer constructor / wrapper constructor
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  3. call QueryInterface to get ITest
  4. call methods on instance
  5. call Release on instance

Clients using scripting interface:

TestVBS.vbs:

WScript.Echo "COM/VBS:"
Set test = CreateObject("Test.CoTest")
WScript.Echo "123 + 456 = " & CStr(test.Add(CLng(123), CLng(456)))
WScript.Echo "ABC + XYZ = " & test.Concat("ABC", "XYZ")
WScript.Echo "IV = " & CStr(test.IV) & " SV = " & test.SV
test.IV = 123
test.SV = "ABC"
WScript.Echo "IV = " & CStr(test.IV) & " SV = " & test.SV
Set test = Nothing

Build and run:

cscript TestVBS.vbs

TestCSDyn.cs:

using System;

public class MainClass
{
    public static void Main(string[] args)
    {
        Console.WriteLine("COM/C# dynamic");
        dynamic test = Activator.CreateInstance(Type.GetTypeFromProgID("Test.CoTest"));
        Console.WriteLine("123 + 456 = {0}", test.Add(123, 456));
        Console.WriteLine("ABC + XYZ = {0}", test.Concat("ABC", "XYZ"));
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
        test.IV = 123;
        test.SV = "ABC";
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
    }
}

Build and run:

csc /platform:x86 TestCSDyn.cs
TestCSDyn

TestPasDyn.pas:

program TestPasDyn;

uses
  ActiveX,ComObj;

var
  test : Variant;

begin
  writeln('COM/Pascal dynamic:');
  CoInitialize(nil);
  test := CreateOLEObject('Test.CoTest');
  writeln('123 + 456 = ', test.Add(123, 456));
  writeln('ABC + XYZ = ', test.Concat('ABC', 'XYZ'));
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  test.IV := 123;
  test.SV := 'ABC';
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  CoUninitialize;
end.

Build and run:

fpc TestPasDyn.pas
TestPasDyn

TestJava.java:

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Variant;

public class TestJava {
    public static void main(String[] args) {
        System.out.println("COM/Java with Jacob ActiveXComponent");
        ActiveXComponent test = new ActiveXComponent("Test.CoTest");
        System.out.println("123 + 456 = " + test.invoke("Add", new Variant(123), new Variant(456)).getInt());
        System.out.println("ABC + XYZ = " + test.invoke("Concat", new Variant("ABC"), new Variant("XYZ")).getString());
        System.out.println("IV = " + test.getPropertyAsInt("IV") + " SV = " + test.getPropertyAsString("SV"));
        test.setProperty("IV", 123);
        test.setProperty("SV", "ABC");
        System.out.println("IV = " + test.getPropertyAsInt("IV") + " SV = " + test.getPropertyAsString("SV"));
    }
}

Build and run:

javac -cp %JACOBDIR%\jacob.jar TestJava.java
java -Djava.library.path=%JACOBDIR% -cp .;%JACOBDIR%\jacob.jar TestJava

Nobody will use IDispatch with C++, but it is still relevant to see the code to understand what is happening in other languages.

TestCPP5.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ via IDispatch:\n");
    CLSID clsid;
    res = CLSIDFromProgID(L"Test.CoTest", &CLSid);
    ReturnCheck(_T("CLSIDFromProgID"), res);
    IDispatch *pDisp = NULL;
    res = CoCreateInstance(clsid, NULL, CLSCTX_SERVER, IID_IDispatch, (void **)&pDisp);
    ReturnCheck(_T("CoCreateInstance"), res);
    DISPID dispid;
    LPOLESTR func;
    VARIANT args[2], retval;
    DISPPARAMS params;
    params.rgvarg = args;
    params.rgdispidNamedArgs = 0;
    params.cNamedArgs = 0;
    // Add
    params.cArgs = 2;
    func = L"Add";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_I4;
    args[0].lVal = 456;
    VariantInit(&args[1]);
    args[1].vt = VT_I4;
    args[1].lVal = 123;
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("123 + 345 = %d\n", retval.lVal);
    // Concat
    params.cArgs = 2;
    func = L"Concat";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_BSTR;
    args[0].bstrVal = SysAllocString(L"ABC");
    VariantInit(&args[1]);
    args[1].vt = VT_BSTR;
    args[1].bstrVal = SysAllocString(L"XYZ");
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("3 ABC + 3 XYZ = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    // Get
    params.cArgs = 0;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("IV = %d", retval.lVal);
    params.cArgs = 0;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf(" SV = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    // Put
    DISPID temp = DISPID_PROPERTYPUT; // these 3 lines are necessary
    params.rgdispidNamedArgs = &temp;
    params.cNamedArgs = 1;
    //
    params.cArgs = 1;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_I4;
    args[0].lVal = 123;
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &params, NULL, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    params.cArgs = 1;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_BSTR;
    args[0].bstrVal = SysAllocString(L"ABC");
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &params, NULL, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    //
    params.rgdispidNamedArgs = 0;
    params.cNamedArgs = 0;
    // Get
    params.cArgs = 0;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("IV = %d", retval.lVal);
    params.cArgs = 0;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf(" SV = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    //
    pDisp->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP5.cpp ole32.lib oleaut32.lib
TestCPP5

TestPHP.php:

<?php
echo "COM/PHP:\r\n";
$test = new com('Test.CoTest'); 
echo sprintf("123 + 456 = %d\r\n", $test->Add(123, 456));
echo sprintf("ABC + XYZ = %s\r\n", $test->Concat("ABC", "XYZ"));
echo sprintf("IV = %d SV = %s\r\n", $test->IV, $test->SV);
$test->IV = 123;
$test->SV = "ABC";
echo sprintf("IV = %d SV = %s\r\n", $test->IV, $test->SV);
?>

Build and run:

php TestPHP.php

TestPy.py:

from win32com.client import *
import pythoncom

print("COM/Python win32:")
test = Dispatch("Test.CoTest")
print("123 + 345 = %d" % test.Add(123, 456))
print("ABC + XYZ = %s" % test.Concat("ABC", "XYZ"))
print("IV = %d DSV = %s" % (test.IV, test.SV))
test.IV = 123
test.SV = "ABC"
print("IV = %d SV = %s" % (test.IV, test.SV))

Build and run:

python TestPy.py

What is happening:

  1. CreateObject / CreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance to get instance (calls AddRef on instance)
    5. call QueryInterface to get IDispatch
  2. call methods on instance
    1. use GetIDsOfNames to translate name to method
    2. use Invoke to call method
  3. set reference to null or let reference go out of scope
    1. call Release on instance

Iteration #11:

In this iteration we will:

Note that the second interface is not available using scripting interface.

Note that default value is not possible in all languages,

Server:

Test.idl:

import "oaidl.idl";

// ITest2
[object, uuid(2F237FFD-A218-4056-8258-7D8A731D8B8F), oleautomation, dual, pointer_default(unique)]
interface ITest2 : IDispatch
{
    [id(1)] HRESULT Sum([in,defaultvalue(0)] long v1, [in,defaultvalue(0)] long v2, [in,defaultvalue(0)] long v3, [in,defaultvalue(0)] long v4, [in,defaultvalue(0)] long v5,
                        [in,defaultvalue(0)] long v6, [in,defaultvalue(0)] long v7, [in,defaultvalue(0)] long v8, [in,defaultvalue(0)] long v9, [in,defaultvalue(0)] long v10,
                        [out,retval] long *res);
}

// ITest
[object, uuid(F621FEC0-3FE3-4256-AE47-22DAC5CDA176), oleautomation, dual, pointer_default(unique)]
interface ITest : IDispatch
{
    [id(1)] HRESULT Add([in] long a, [in,defaultvalue(1)] long b, [out,retval] long *c);
    [id(2)] HRESULT Concat([in] BSTR a, [in,defaultvalue(L"X")] BSTR b, [out,retval] BSTR *c);
    [id(3),propget] HRESULT IV([out, retval] long *iv);
    [id(3),propput] HRESULT IV([in] long iv);
    [id(4),propget] HRESULT SV([out, retval] BSTR *sv);
    [id(4),propput] HRESULT SV([in] BSTR sv);
    [id(5)] HRESULT Another([out,retval] ITest **test);
}

// Test library
[uuid(61E1E003-C924-4df7-A443-2E865C87663D), version(1.0)]
library TestLibrary
{
    importlib("stdole32.tlb");
    [uuid(8CD6DD8F-B2E8-4e3f-BF00-37B7F9C4FB2A)]
    coclass CoTest
    {
        [default] interface ITest;
        interface ITest2;
    };
};

TestEx.h:

#ifndef TESTEX_H
#define TESTEX_H

#include <windows.h>
#include <tchar.h>

#include <atlbase.h>
#include <atlcom.h>

#include "Test_i.c"
#include "Test.h"

class ATL_NO_VTABLE CCoTest : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCoTest, &CLSID_CoTest>, public IDispatchImpl<ITest, &IID_ITest, &LIBID_TestLibrary>, public IDispatchImpl<ITest2, &IID_ITest2, &LIBID_TestLibrary>
{
private:
    long m_iv;
    BSTR m_sv;
public:
    CCoTest();
    virtual ~CCoTest();
    BEGIN_COM_MAP(CCoTest)
       COM_INTERFACE_ENTRY(ITest)
       COM_INTERFACE_ENTRY(ITest2)
       COM_INTERFACE_ENTRY2(IDispatch, ITest)
    END_COM_MAP()
    // ITest
    STDMETHODIMP Add(long a, long b, long *c);
    STDMETHODIMP Concat(BSTR a, BSTR b, BSTR *c);
    STDMETHODIMP get_IV(long *iv);
    STDMETHODIMP put_IV(long iv);
    STDMETHODIMP get_SV(BSTR *sv);
    STDMETHODIMP put_SV(BSTR sv);
    STDMETHODIMP Another(ITest **test);
    // ITest2
    STDMETHODIMP Sum(long v1, long v2, long v3, long v4, long v5, long v6, long v7, long v8, long v9, long v10, long *res);
    DECLARE_REGISTRY(CLSID_CoTest, _T("Test.CoTest.1"), _T("Test.CoTest"), 0U, THREADFLAGS_APARTMENT)
};

#endif // TESTEX_H

Test.cpp:

#include <stdio.h>

#include <windows.h>
#include <oleauto.h>
#include <initguid.h>

#include <atlbase.h>
extern CComModule _Module;
#include <atlcom.h>

#include "TestEx.h"

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
    OBJECT_ENTRY(CLSID_CoTest, CCoTest)
END_OBJECT_MAP()

CCoTest::CCoTest()
{
#ifdef DEBUG
    printf("CCoTest ctor\n");
#endif
    m_iv = 0;
    m_sv = SysAllocString(L"");
}

CCoTest::~CCoTest()
{
#ifdef DEBUG
    printf("CCoTest dtor\n");
#endif
    SysFreeString(m_sv);
}

STDMETHODIMP CCoTest::Add(long a, long b, long *c)
{
    *c = a + b;
    return S_OK;
}

STDMETHODIMP CCoTest::Concat(BSTR a, BSTR b, BSTR *c)
{
    OLECHAR *buf = new OLECHAR[SysStringLen(a) + SysStringLen(b) + 1];
    wcscpy(buf, a);
    wcscat(buf, b);
    *c = SysAllocString(buf);
    delete[] buf;
    return S_OK;
}

STDMETHODIMP CCoTest::get_IV(long *iv)
{
    *iv = m_iv;
    return S_OK;
}

STDMETHODIMP CCoTest::put_IV(long iv)
{
    m_iv = iv;
    return S_OK;
}

STDMETHODIMP CCoTest::get_SV(BSTR *sv)
{
    *sv = SysAllocString(m_sv);
    return S_OK;
}

STDMETHODIMP CCoTest::put_SV(BSTR sv)
{
    SysReAllocString(&m_sv, sv);
    return S_OK;
}

STDMETHODIMP CCoTest::Another(ITest **test)
{
    CComObject<CCoTest> *pTest;
    CComObject<CCoTest>::CreateInstance(&pTest);
    pTest->QueryInterface(IID_ITest, (void **)test);
    return S_OK;
}

STDMETHODIMP CCoTest::Sum(long v1, long v2, long v3, long v4, long v5, long v6, long v7, long v8, long v9, long v10, long *res)
{
    *res = v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10;
    return S_OK;
}

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
    if(dwReason == DLL_PROCESS_ATTACH)
    {
        _Module.Init(ObjectMap, hInstance, &LIBID_TestLibrary);
    }
    else if(dwReason == DLL_PROCESS_DETACH)
    {
        _Module.Term();
    }
    return TRUE;
}

STDMETHODIMP DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("DllGetClassObject\n");
#endif
    return _Module.GetClassObject(rclsid, riid, ppv);
}

STDMETHODIMP DllCanUnloadNow()
{
    return (_Module.GetLockCount() == 0) ? S_OK : S_FALSE;
}

STDMETHODIMP DllRegisterServer()
{
    return _Module.RegisterServer(TRUE);
}

STDMETHODIMP DllUnregisterServer()
{
    return _Module.UnregisterServer(TRUE);
}

Test.def:

LIBRARY "Test"
EXPORTS
    DllMain             PRIVATE
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE
    DllRegisterServer   PRIVATE
    DllUnregisterServer PRIVATE

Build and registration:

midl Test.idl
cl /LD Test.cpp Test.def oleaut32.lib
regsvr32 Test.dll

Clients using header file:

TestC.c:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#define COBJMACROS
#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    ITest *pTest, *another, *temp;
    ITest2 *pTest2;
    int v, iv, sum;
    BSTR s1, s2, s3, sv;
    CoInitialize(NULL);
    printf("COM/C:\n");
    res = CoCreateInstance(&CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, &IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    res = ITest_Add(pTest, 123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 345 = %d\n", v);
    s1 = SysAllocString(L"ABC");
    s2 = SysAllocString(L"XYZ");
    res = ITest_Concat(pTest, s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    ITest_get_IV(pTest, &iv);
    ITest_get_SV(pTest, &sv);
    printf("IV = %d SV = %d %ls\n", iv, SysStringLen(sv), sv);
    SysFreeString(sv);
    ITest_put_IV(pTest, 123);
    ITest_put_SV(pTest, SysAllocString(L"ABC"));
    ITest_get_IV(pTest, &iv);
    ITest_get_SV(pTest, &sv);
    printf("IV = %d SV = %d %ls\n", iv, SysStringLen(sv), sv);
    SysFreeString(sv);
    ITest_Another(pTest, &another);
    res = ITest_Add(another, 123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("Another 123 + 345 = %d\n", v);
    ITest_Release(another);
    ITest_Release(pTest);
    res = CoCreateInstance(&CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, &IID_ITest, (void**)&temp);
    ReturnCheck(_T("CoCreateInstance"), res);
    res = ITest2_QueryInterface(temp, &IID_ITest2, &pTest2);
    ReturnCheck(_T("QueryInterface"), res);
    ITest2_Sum(pTest2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, &sum);
    printf("SUM = %d\n", sum);
    ITest2_Sum(pTest2, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, &sum);
    printf("SUM 1..5 = %d\n", sum);
    ITest2_Sum(pTest2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, &sum);
    printf("SUM 1..10 = %d\n", sum);
    ITest2_Release (pTest2);
    ITest_Release (temp);
    CoUninitialize();
    return 0;
}

Build and run:

cl TestC.c Test_i.c ole32.lib oleaut32.lib
TestC

What is happening:

  1. CoGetClassObject and CreateInstance / CoCreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  2. call QueryInterface to get ITest
  3. call methods on instance
  4. call Release on instance

Clients using type library:

TestCPP3.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CreateInstance:\n");
    try
    {
        ITestPtr spTest;
        res = spTest.CreateInstance(CLSID_CoTest);
        ReturnCheck(_T("CreateInstance"), res);
        long v = spTest->Add(123L, 456L);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        BSTR sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest->PutIV(123);
        spTest->PutSV(SysAllocString(L"ABC"));
        sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        ITestPtr another = spTest->Another();
        printf("Another 123 + 345 = %d\n", another->Add(123L, 456L));
        another = NULL;
        spTest = NULL;
        ITestPtr temp;
        res = temp.CreateInstance(CLSID_CoTest);
        ReturnCheck(_T("CreateInstance"), res);
        ITest2Ptr spTest2 = temp;
        printf("SUM = %d\n", spTest2->Sum(0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
        printf("SUM 1..5 = %d\n", spTest2->Sum(1, 2, 3, 4, 5, 0, 0, 0, 0, 0));
        printf("SUM 1..10 = %d\n", spTest2->Sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
        spTest2 = NULL;
        temp = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP3.cpp ole32.lib oleaut32.lib
TestCPP3

TestCPP4.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with constructor:\n");
    try
    {
        ITestPtr spTest(__uuidof(CoTest));
        long v = spTest->Add(123, 456);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        BSTR sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest->PutIV(123);
        spTest->PutSV(SysAllocString(L"ABC"));
        sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        ITestPtr another = spTest->Another();
        printf("Another 123 + 345 = %d\n", another->Add(123L, 456L));
        another = NULL;
        spTest = NULL;
        ITestPtr temp(__uuidof(CoTest));
        ITest2Ptr spTest2 = temp;
        printf("SUM = %d\n", spTest2->Sum(0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
        printf("SUM 1..5 = %d\n", spTest2->Sum(1, 2, 3, 4, 5, 0, 0, 0, 0, 0));
        printf("SUM 1..10 = %d\n", spTest2->Sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
        spTest2 = NULL;
        temp = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP4.cpp ole32.lib oleaut32.lib
TestCPP4

TestCS.cpp:

using System;

using Test;

public class MainClass
{
    public static void Main(string[] args)
    {
        Console.WriteLine("COM/C# static");
        CoTest cotest = new CoTestClass();
        ITest test = (ITest)cotest;
        Console.WriteLine("123 + 456 = {0}", test.Add(123, 456));
        Console.WriteLine("123 + default = {0}", test.Add(123));
        Console.WriteLine("ABC + XYZ = {0}", test.Concat("ABC", "XYZ"));
        Console.WriteLine("ABC + default = {0}", test.Concat("ABC"));
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
        test.IV = 123;
        test.SV = "ABC";
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
        CoTest another = test.Another();
        Console.WriteLine("Another 123 + 345 = {0}", another.Add(123, 456));
        CoTest cotest2 = new CoTestClass();
        ITest2 test2 = (ITest2)cotest2;
        Console.WriteLine("Sum = {0}", test2.Sum());
        Console.WriteLine("Sum 1..5 = {0}", test2.Sum(1, 2, 3, 4, 5));
        Console.WriteLine("Sum 1..10 = {0}", test2.Sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
    }
}

Build and run:

tlbimp /out=TestWrap.dll /namespace:Test Test.tlb
csc /platform:x86 /r:TestWrap.dll TestCS.cs
TestCS

TestPas.pas:

program TestPas;

{$mode delphi}{$H+}

uses
  ActiveX, TestLibrary_1_0_TLB;

var
  test, another, temp : ITest;
  test2 : ITest2;

begin
  writeln('COM/Pascal static:');
  CoInitialize(nil);
  test := CoCoTest.Create;
  writeln('123 + 456 = ', test.Add(123, 456));
  writeln('ABC + XYZ = ', test.Concat('ABC', 'XYZ'));
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  test.IV := 123;
  test.SV := 'ABC';
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  another := test.Another; (* seems to leak a reference ???? *)
  writeln('Another 123 + 456 = ', another.Add(123, 456));
  temp := CoCoTest.Create;
  temp.QueryInterface(IID_ITest2, test2);
  writeln('Sum = ', test2.Sum(0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
  writeln('Sum 1..5 = ', test2.Sum(1, 2, 3, 4, 5, 0, 0, 0, 0, 0));
  writeln('Sum 1..10 = ', test2.Sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
  CoUninitialize;
end.

Build and run:

importtl Test.tlb
fpc TestLibrary_1_0_TLB.pas
fpc TestPas.pas
TestPas

What is happening:

  1. import TLB
  2. pointer CreateInstance / pointer constructor / wrapper constructor
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  3. call QueryInterface to get ITest
  4. call methods on instance
  5. call Release on instance

Clients using scripting interface:

TestVBS.vbs:

WScript.Echo "COM/VBS:"
Set test = CreateObject("Test.CoTest")
WScript.Echo "123 + 456 = " & CStr(test.Add(CLng(123), CLng(456)))
WScript.Echo "123 + default = " & CStr(test.Add(CLng(123)))
WScript.Echo "ABC + XYZ = " & test.Concat("ABC", "XYZ")
WScript.Echo "ABC + default = " & test.Concat("ABC")
WScript.Echo "IV = " & CStr(test.IV) & " SV = " & test.SV
test.IV = 123
test.SV = "ABC"
WScript.Echo "IV = " & CStr(test.IV) & " SV = " & test.SV
Set another = test.Another()
WScript.Echo "Another 123 + 456 = " & CStr(another.Add(CLng(123), CLng(456)))
Set another = Nothing
' second non-default interface is not accessible
Set test = Nothing

Build and run:

cscript TestVBS.vbs

TestCSDyn.cs:

using System;

public class MainClass
{
    public static void Main(string[] args)
    {
        Console.WriteLine("COM/C# dynamic");
        dynamic test = Activator.CreateInstance(Type.GetTypeFromProgID("Test.CoTest"));
        Console.WriteLine("123 + 456 = {0}", test.Add(123, 456));
        Console.WriteLine("123 + default = {0}", test.Add(123));
        Console.WriteLine("ABC + XYZ = {0}", test.Concat("ABC", "XYZ"));
        Console.WriteLine("ABC + default = {0}", test.Concat("ABC"));
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
        test.IV = 123;
        test.SV = "ABC";
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
        dynamic another = test.Another();
        Console.WriteLine("Another 123 + 345 = {0}", another.Add(123, 456));
        // second non-default interface is not accessible
    }
}

Build and run:

csc /platform:x86 TestCSDyn.cs
TestCSDyn

TestPasDyn.pas:

program TestPasDyn;

uses
  ActiveX,ComObj;

var
  test, another : Variant;

begin
  writeln('COM/Pascal dynamic:');
  CoInitialize(nil);
  test := CreateOLEObject('Test.CoTest');
  writeln('123 + 456 = ', test.Add(123, 456));
  writeln('123 + default = ', test.Add(123));
  writeln('ABC + XYZ = ', test.Concat('ABC', 'XYZ'));
  writeln('ABC + default = ', test.Concat('ABC'));
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  test.IV := 123;
  test.SV := 'ABC';
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  another := test.Another;
  writeln('Another 123 + 456 = ', another.Add(123, 456));
  (* second non-default interface is not accessible *)
  CoUninitialize;
end.

Build and run:

fpc TestPasDyn.pas
TestPasDyn

TestJava.java:

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Variant;

public class TestJava {
    public static void main(String[] args) {
        System.out.println("COM/Java with Jacob ActiveXComponent");
        ActiveXComponent test = new ActiveXComponent("Test.CoTest");
        System.out.println("123 + 456 = " + test.invoke("Add", new Variant(123), new Variant(456)).getInt());
        System.out.println("123 + default = " + test.invoke("Add", new Variant(123)).getInt());
        System.out.println("ABC + XYZ = " + test.invoke("Concat", new Variant("ABC"), new Variant("XYZ")).getString());
        System.out.println("ABC + default = " + test.invoke("Concat", new Variant("ABC")).getString());
        System.out.println("IV = " + test.getPropertyAsInt("IV") + " SV = " + test.getPropertyAsString("SV"));
        test.setProperty("IV", 123);
        test.setProperty("SV", "ABC");
        System.out.println("IV = " + test.getPropertyAsInt("IV") + " SV = " + test.getPropertyAsString("SV"));
        ActiveXComponent another = test.invokeGetComponent("Another");
        System.out.println("Another 123 + 456 = " + another.invoke("Add", new Variant(123), new Variant(456)).getInt());
        // second non-default interface is not accessible
    }
}

Build and run:

javac -cp %JACOBDIR%\jacob.jar TestJava.java
java -Djava.library.path=%JACOBDIR% -cp .;%JACOBDIR%\jacob.jar TestJava

Nobody will use IDispatch with C++, but it is still relevant to see the code to understand what is happening in other languages.

TestCPP5.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ via IDispatch:\n");
    CLSID clsid;
    res = CLSIDFromProgID(L"Test.CoTest", &clsid);
    ReturnCheck(_T("CLSIDFromProgID"), res);
    IDispatch *pDisp = NULL;
    res = CoCreateInstance(clsid, NULL, CLSCTX_SERVER, IID_IDispatch, (void **)&pDisp);
    ReturnCheck(_T("CoCreateInstance"), res);
    DISPID dispid;
    LPOLESTR func;
    VARIANT args[2], retval;
    DISPPARAMS params;
    params.rgvarg = args;
    params.rgdispidNamedArgs = 0;
    params.cNamedArgs = 0;
    // Add
    params.cArgs = 2;
    func = L"Add";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_I4;
    args[0].lVal = 123;
    VariantInit(&args[1]);
    args[1].vt = VT_I4;
    args[1].lVal = 456;
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("123 + 456 = %d\n", retval.lVal);
    // Add default
    params.cArgs = 1;
    func = L"Add";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_I4;
    args[0].lVal = 123;
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("123 + default = %d\n", retval.lVal);
    // Concat
    params.cArgs = 2;
    func = L"Concat";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_BSTR;
    args[0].bstrVal = SysAllocString(L"ABC");
    VariantInit(&args[1]);
    args[1].vt = VT_BSTR;
    args[1].bstrVal = SysAllocString(L"XYZ");
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("3 ABC + 3 XYZ = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    // Concat default
    params.cArgs = 1;
    func = L"Concat";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_BSTR;
    args[0].bstrVal = SysAllocString(L"ABC");
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("3 ABC + default = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    // Get
    params.cArgs = 0;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("IV = %d", retval.lVal);
    params.cArgs = 0;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf(" SV = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    // Put
    DISPID temp = DISPID_PROPERTYPUT; // these 3 lines are necessary
    params.rgdispidNamedArgs = &temp;
    params.cNamedArgs = 1;
    //
    params.cArgs = 1;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_I4;
    args[0].lVal = 123;
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &params, NULL, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    params.cArgs = 1;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_BSTR;
    args[0].bstrVal = SysAllocString(L"ABC");
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &params, NULL, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    //
    params.rgdispidNamedArgs = 0;
    params.cNamedArgs = 0;
    // Get
    params.cArgs = 0;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("IV = %d", retval.lVal);
    params.cArgs = 0;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf(" SV = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    // second non-default interface is not accessible
    //
    pDisp->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP5.cpp ole32.lib oleaut32.lib
TestCPP5

TestPHP.php:

<?php
echo "COM/PHP:\r\n";
$test = new com('Test.CoTest'); 
echo sprintf("123 + 456 = %d\r\n", $test->Add(123, 456));
echo sprintf("123 + default = %d\r\n", $test->Add(123));
echo sprintf("ABC + XYZ = %s\r\n", $test->Concat("ABC", "XYZ"));
echo sprintf("ABC + default = %s\r\n", $test->Concat("ABC"));
echo sprintf("IV = %d SV = %s\r\n", $test->IV, $test->SV);
$test->IV = 123;
$test->SV = "ABC";
echo sprintf("IV = %d SV = %s\r\n", $test->IV, $test->SV);
$another = $test->Another();
echo sprintf("Another 123 + 456 = %d\r\n", $another->Add(123, 456));
// second non-default interface is not accessible
?>

Build and run:

php TestPHP.php

TestPy.py:

from win32com.client import *
import pythoncom

print("COM/Python win32:")
test = Dispatch("Test.CoTest")
print("123 + 345 = %d" % test.Add(123, 456))
print("123 + default = %d" % test.Add(123))
print("ABC + XYZ = %s" % test.Concat("ABC", "XYZ"))
print("ABC + default = %s" % test.Concat("ABC"))
print("IV = %d DSV = %s" % (test.IV, test.SV))
test.IV = 123
test.SV = "ABC"
print("IV = %d SV = %s" % (test.IV, test.SV))
another = test.Another()
print("Another 123 + 345 = %d" % another.Add(123, 456))
# second non-default interface is not accessible

Build and run:

python TestPy.py

What is happening:

  1. CreateObject / CreateInstance
    1. translate from CLSID to DLL via registry
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance to get instance (calls AddRef on instance)
    5. call QueryInterface to get IDispatch
  2. call methods on instance
    1. use GetIDsOfNames to translate name to method
    2. use Invoke to call method
  3. set reference to null or let reference go out of scope
    1. call Release on instance

Registry free:

Now we will try drop the use of registry and use manifests instead.

Note that manifests only work with some languages.

Server:

Test.idl:

import "oaidl.idl";

// ITest2
[object, uuid(2F237FFD-A218-4056-8258-7D8A731D8B8F), oleautomation, dual, pointer_default(unique)]
interface ITest2 : IDispatch
{
    [id(1)] HRESULT Sum([in,defaultvalue(0)] long v1, [in,defaultvalue(0)] long v2, [in,defaultvalue(0)] long v3, [in,defaultvalue(0)] long v4, [in,defaultvalue(0)] long v5,
                        [in,defaultvalue(0)] long v6, [in,defaultvalue(0)] long v7, [in,defaultvalue(0)] long v8, [in,defaultvalue(0)] long v9, [in,defaultvalue(0)] long v10,
                        [out,retval] long *res);
}

// ITest
[object, uuid(F621FEC0-3FE3-4256-AE47-22DAC5CDA176), oleautomation, dual, pointer_default(unique)]
interface ITest : IDispatch
{
    [id(1)] HRESULT Add([in] long a, [in,defaultvalue(1)] long b, [out,retval] long *c);
    [id(2)] HRESULT Concat([in] BSTR a, [in,defaultvalue(L"X")] BSTR b, [out,retval] BSTR *c);
    [id(3),propget] HRESULT IV([out, retval] long *iv);
    [id(3),propput] HRESULT IV([in] long iv);
    [id(4),propget] HRESULT SV([out, retval] BSTR *sv);
    [id(4),propput] HRESULT SV([in] BSTR sv);
    [id(5)] HRESULT Another([out,retval] ITest **test);
}

// Test library
[uuid(61E1E003-C924-4df7-A443-2E865C87663D), version(1.0)]
library TestLibrary
{
    importlib("stdole32.tlb");
    [uuid(8CD6DD8F-B2E8-4e3f-BF00-37B7F9C4FB2A)]
    coclass CoTest
    {
        [default] interface ITest;
        interface ITest2;
    };
};

TestEx.h:

#ifndef TESTEX_H
#define TESTEX_H

#include <windows.h>
#include <tchar.h>

#include <atlbase.h>
#include <atlcom.h>

#include "Test_i.c"
#include "Test.h"

class ATL_NO_VTABLE CCoTest : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCoTest, &CLSID_CoTest>, public IDispatchImpl<ITest, &IID_ITest, &LIBID_TestLibrary>, public IDispatchImpl<ITest2, &IID_ITest2, &LIBID_TestLibrary>
{
private:
    long m_iv;
    BSTR m_sv;
public:
    CCoTest();
    virtual ~CCoTest();
    BEGIN_COM_MAP(CCoTest)
       COM_INTERFACE_ENTRY(ITest)
       COM_INTERFACE_ENTRY(ITest2)
       COM_INTERFACE_ENTRY2(IDispatch, ITest)
    END_COM_MAP()
    // ITest
    STDMETHODIMP Add(long a, long b, long *c);
    STDMETHODIMP Concat(BSTR a, BSTR b, BSTR *c);
    STDMETHODIMP get_IV(long *iv);
    STDMETHODIMP put_IV(long iv);
    STDMETHODIMP get_SV(BSTR *sv);
    STDMETHODIMP put_SV(BSTR sv);
    STDMETHODIMP Another(ITest **test);
    // ITest2
    STDMETHODIMP Sum(long v1, long v2, long v3, long v4, long v5, long v6, long v7, long v8, long v9, long v10, long *res);
    DECLARE_REGISTRY(CLSID_CoTest, _T("Test.CoTest.1"), _T("Test.CoTest"), 0U, THREADFLAGS_APARTMENT)
};

#endif // TESTEX_H

Test.cpp:

#include <stdio.h>

#include <windows.h>
#include <oleauto.h>
#include <initguid.h>

#include <atlbase.h>
extern CComModule _Module;
#include <atlcom.h>

#include "TestEx.h"

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
    OBJECT_ENTRY(CLSID_CoTest, CCoTest)
END_OBJECT_MAP()

CCoTest::CCoTest()
{
#ifdef DEBUG
    printf("CCoTest ctor\n");
#endif
    m_iv = 0;
    m_sv = SysAllocString(L"");
}

CCoTest::~CCoTest()
{
#ifdef DEBUG
    printf("CCoTest dtor\n");
#endif
    SysFreeString(m_sv);
}

STDMETHODIMP CCoTest::Add(long a, long b, long *c)
{
    *c = a + b;
    return S_OK;
}

STDMETHODIMP CCoTest::Concat(BSTR a, BSTR b, BSTR *c)
{
    OLECHAR *buf = new OLECHAR[SysStringLen(a) + SysStringLen(b) + 1];
    wcscpy(buf, a);
    wcscat(buf, b);
    *c = SysAllocString(buf);
    delete[] buf;
    return S_OK;
}

STDMETHODIMP CCoTest::get_IV(long *iv)
{
    *iv = m_iv;
    return S_OK;
}

STDMETHODIMP CCoTest::put_IV(long iv)
{
    m_iv = iv;
    return S_OK;
}

STDMETHODIMP CCoTest::get_SV(BSTR *sv)
{
    *sv = SysAllocString(m_sv);
    return S_OK;
}

STDMETHODIMP CCoTest::put_SV(BSTR sv)
{
    SysReAllocString(&m_sv, sv);
    return S_OK;
}

STDMETHODIMP CCoTest::Another(ITest **test)
{
    CComObject<CCoTest> *pTest;
    CComObject<CCoTest>::CreateInstance(&pTest);
    pTest->QueryInterface(IID_ITest, (void **)test);
    return S_OK;
}

STDMETHODIMP CCoTest::Sum(long v1, long v2, long v3, long v4, long v5, long v6, long v7, long v8, long v9, long v10, long *res)
{
    *res = v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + v10;
    return S_OK;
}

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
    if(dwReason == DLL_PROCESS_ATTACH)
    {
        _Module.Init(ObjectMap, hInstance, &LIBID_TestLibrary);
    }
    else if(dwReason == DLL_PROCESS_DETACH)
    {
        _Module.Term();
    }
    return TRUE;
}

STDMETHODIMP DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("DllGetClassObject\n");
#endif
    return _Module.GetClassObject(rclsid, riid, ppv);
}

STDMETHODIMP DllCanUnloadNow()
{
    return (_Module.GetLockCount() == 0) ? S_OK : S_FALSE;
}

STDMETHODIMP DllRegisterServer()
{
    return _Module.RegisterServer(TRUE);
}

STDMETHODIMP DllUnregisterServer()
{
    return _Module.UnregisterServer(TRUE);
}

Test.def:

LIBRARY "Test"
EXPORTS
    DllMain             PRIVATE
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE
    DllRegisterServer   PRIVATE
    DllUnregisterServer PRIVATE

Build and registration:

midl Test.idl
cl /LD Test.cpp Test.def oleaut32.lib
mt -dll:Test.dll -tlb:Test.tlb -out:Test.dll.manifest
cscript replace.vbs "Test.dll.manifest" "<comClass clsid=#{CFF35242-7454-4B39-AAD8-138661FD4E01}#" "<comClass progid=#Test.CoTest# clsid=#{CFF35242-7454-4B39-AAD8-138661FD4E01}#"

Clients using header file:

TestC.c:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#define COBJMACROS
#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    ITest *pTest, *another, *temp;
    ITest2 *pTest2;
    int v, iv, sum;
    BSTR s1, s2, s3, sv;
    CoInitialize(NULL);
    printf("COM/C:\n");
    res = CoCreateInstance(&CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, &IID_ITest, (void**)&pTest);
    ReturnCheck(_T("CoCreateInstance"), res);
    res = ITest_Add(pTest, 123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 345 = %d\n", v);
    s1 = SysAllocString(L"ABC");
    s2 = SysAllocString(L"XYZ");
    res = ITest_Concat(pTest, s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    ITest_get_IV(pTest, &iv);
    ITest_get_SV(pTest, &sv);
    printf("IV = %d SV = %d %ls\n", iv, SysStringLen(sv), sv);
    SysFreeString(sv);
    ITest_put_IV(pTest, 123);
    ITest_put_SV(pTest, SysAllocString(L"ABC"));
    ITest_get_IV(pTest, &iv);
    ITest_get_SV(pTest, &sv);
    printf("IV = %d SV = %d %ls\n", iv, SysStringLen(sv), sv);
    SysFreeString(sv);
    ITest_Another(pTest, &another);
    res = ITest_Add(another, 123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("Another 123 + 345 = %d\n", v);
    ITest_Release(another);
    ITest_Release(pTest);
    res = CoCreateInstance(&CLSID_CoTest, NULL, CLSCTX_INPROC_SERVER, &IID_ITest, (void**)&temp);
    ReturnCheck(_T("CoCreateInstance"), res);
    res = ITest2_QueryInterface(temp, &IID_ITest2, &pTest2);
    ReturnCheck(_T("QueryInterface"), res);
    ITest2_Sum(pTest2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, &sum);
    printf("SUM = %d\n", sum);
    ITest2_Sum(pTest2, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0, &sum);
    printf("SUM 1..5 = %d\n", sum);
    ITest2_Sum(pTest2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, &sum);
    printf("SUM 1..10 = %d\n", sum);
    ITest2_Release (pTest2);
    ITest_Release (temp);
    CoUninitialize();
    return 0;
}

TestC.exe.manifest:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
    <assemblyIdentity type="win32" name="TestC.exe" version="1.0.0.0" />
    <dependency>
        <dependentAssembly>
            <assemblyIdentity type="win32" name="Test.dll" version="1.0.0.0" />
        </dependentAssembly>
    </dependency>
</assembly>

Build and run:

cl TestC.c Test_i.c ole32.lib oleaut32.lib
TestC

What is happening:

  1. CoGetClassObject and CreateInstance / CoCreateInstance
    1. translate from CLSID to DLL via manifest files
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  2. call QueryInterface to get ITest
  3. call methods on instance
  4. call Release on instance

Clients using type library:

TestCPP3.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CreateInstance:\n");
    try
    {
        ITestPtr spTest;
        res = spTest.CreateInstance(CLSID_CoTest);
        ReturnCheck(_T("CreateInstance"), res);
        long v = spTest->Add(123L, 456L);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        BSTR sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest->PutIV(123);
        spTest->PutSV(SysAllocString(L"ABC"));
        sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        ITestPtr another = spTest->Another();
        printf("Another 123 + 345 = %d\n", another->Add(123L, 456L));
        another = NULL;
        spTest = NULL;
        ITestPtr temp;
        res = temp.CreateInstance(CLSID_CoTest);
        ReturnCheck(_T("CreateInstance"), res);
        ITest2Ptr spTest2 = temp;
        printf("SUM = %d\n", spTest2->Sum(0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
        printf("SUM 1..5 = %d\n", spTest2->Sum(1, 2, 3, 4, 5, 0, 0, 0, 0, 0));
        printf("SUM 1..10 = %d\n", spTest2->Sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
        spTest2 = NULL;
        temp = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

TestCPP3.exe.manifest:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
    <assemblyIdentity type="win32" name="TestCPP3.exe" version="1.0.0.0" />
    <dependency>
        <dependentAssembly>
            <assemblyIdentity type="win32" name="Test.dll" version="1.0.0.0" />
        </dependentAssembly>
    </dependency>
</assembly>

Build and run:

cl /EHsc TestCPP3.cpp ole32.lib oleaut32.lib
TestCPP3

TestCPP4.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with constructor:\n");
    try
    {
        ITestPtr spTest(__uuidof(CoTest));
        long v = spTest->Add(123, 456);
        printf("123 + 345 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = spTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        BSTR sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        spTest->PutIV(123);
        spTest->PutSV(SysAllocString(L"ABC"));
        sv = spTest->GetSV();
        printf("IV = %d SV = %d %ls\n", spTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        ITestPtr another = spTest->Another();
        printf("Another 123 + 345 = %d\n", another->Add(123L, 456L));
        another = NULL;
        spTest = NULL;
        ITestPtr temp(__uuidof(CoTest));
        ITest2Ptr spTest2 = temp;
        printf("SUM = %d\n", spTest2->Sum(0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
        printf("SUM 1..5 = %d\n", spTest2->Sum(1, 2, 3, 4, 5, 0, 0, 0, 0, 0));
        printf("SUM 1..10 = %d\n", spTest2->Sum(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
        spTest2 = NULL;
        temp = NULL;
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

TestCPP4.exe.manifest:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
    <assemblyIdentity type="win32" name="TestCPP4.exe" version="1.0.0.0" />
    <dependency>
        <dependentAssembly>
            <assemblyIdentity type="win32" name="Test.dll" version="1.0.0.0" />
        </dependentAssembly>
    </dependency>
</assembly>

Build and run:

cl /EHsc TestCPP4.cpp ole32.lib oleaut32.lib
TestCPP4

What is happening:

  1. import TLB
  2. pointer CreateInstance / pointer constructor / wrapper constructor
    1. translate from CLSID to DLL via manifest files
    2. load DLL
    3. call DllGetClassObject to get instance of factory
    4. call factory CreateInstance with IID to get instance (calls AddRef on instance)
  3. call QueryInterface to get ITest
  4. call methods on instance
  5. call Release on instance

DCOM:

Now we will try and call the COM component remotely.

In process COM look like:

In process COM

Remote COM aka DCOM look like:

Remote COM

MSRPC is the Microsoft flavor of the standard DCE/RPC.

Note that not all COM interfaces support remote access.

Server:

Test.idl:

import "oaidl.idl";

// ITest
[object, uuid(1B255E86-9F4C-442c-8987-947BD9D6AC7C), oleautomation, dual, pointer_default(unique)]
interface ITest : IDispatch
{
    [id(1)] HRESULT Add([in] long a, [in] long b, [out,retval] long *c);
    [id(2)] HRESULT Concat([in] BSTR a, [in] BSTR b, [out,retval]BSTR *c);
    [id(3),propget] HRESULT IV([out, retval] long *iv);
    [id(3),propput] HRESULT IV([in] long iv);
    [id(4),propget] HRESULT SV([out, retval] BSTR *sv);
    [id(4),propput] HRESULT SV([in] BSTR sv);
}

// Test library
[uuid(105A7B5F-67E9-4d10-A22D-B3C989904332), version(1.0)]
library TestLibrary
{
    importlib("stdole32.tlb");
    [uuid(A178C19F-C64B-4a96-8D6C-83698E5B7A70)]
    coclass CoTest
    {
        [default] interface ITest;
    };
};

TestEx.h:

#ifndef TESTEX_H
#define TESTEX_H

#include <windows.h>
#include <tchar.h>

#include <atlbase.h>
#include <atlcom.h>

#include "Test_i.c"
#include "Test.h"

class ATL_NO_VTABLE CCoTest : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CCoTest, &CLSID_CoTest>, public IDispatchImpl<ITest, &IID_ITest, &LIBID_TestLibrary>
{
private:
    long m_iv;
    BSTR m_sv;
public:
    CCoTest();
    virtual ~CCoTest();
    BEGIN_COM_MAP(CCoTest)
       COM_INTERFACE_ENTRY(ITest)
       COM_INTERFACE_ENTRY2(IDispatch, ITest)
    END_COM_MAP()
    // ITest
    STDMETHODIMP Add(long a, long b, long *c);
    STDMETHODIMP Concat(BSTR a, BSTR b, BSTR *c);
    STDMETHODIMP get_IV(long *iv);
    STDMETHODIMP put_IV(long iv);
    STDMETHODIMP get_SV(BSTR *sv);
    STDMETHODIMP put_SV(BSTR sv);
    static HRESULT WINAPI UpdateRegistry(BOOL b)
    {
        return _Module.UpdateRegistryClass(CLSID_CoTest, _T("Test.CoTest.1"), _T("Test.CoTest"), 0U, THREADFLAGS_APARTMENT, b);
    }
};

#endif // TESTEX_H

Test.cpp:

#include <stdio.h>

#include <windows.h>
#include <oleauto.h>
#include <initguid.h>

#include <atlbase.h>
extern CComModule _Module;
#include <atlcom.h>

#include "TestEx.h"

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
    OBJECT_ENTRY(CLSID_CoTest, CCoTest)
END_OBJECT_MAP()

CCoTest::CCoTest()
{
#ifdef DEBUG
    printf("CCoTest ctor\n");
#endif
    m_iv = 0;
    m_sv = SysAllocString(L"");
}

CCoTest::~CCoTest()
{
#ifdef DEBUG
    printf("CCoTest dtor\n");
#endif
    SysFreeString(m_sv);
}

STDMETHODIMP CCoTest::Add(long a, long b, long *c)
{
    *c = a + b;
    return S_OK;
}

STDMETHODIMP CCoTest::Concat(BSTR a, BSTR b, BSTR *c)
{
    OLECHAR *buf = new OLECHAR[SysStringLen(a) + SysStringLen(b) + 1];
    wcscpy(buf, a);
    wcscat(buf, b);
    *c = SysAllocString(buf);
    delete[] buf;
    return S_OK;
}

STDMETHODIMP CCoTest::get_IV(long *iv)
{
    *iv = m_iv;
    return S_OK;
}

STDMETHODIMP CCoTest::put_IV(long iv)
{
    m_iv = iv;
    return S_OK;
}

STDMETHODIMP CCoTest::get_SV(BSTR *sv)
{
    *sv = SysAllocString(m_sv);
    return S_OK;
}

STDMETHODIMP CCoTest::put_SV(BSTR sv)
{
    SysReAllocString(&m_sv, sv);
    return S_OK;
}

BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
    if(dwReason == DLL_PROCESS_ATTACH)
    {
        _Module.Init(ObjectMap, hInstance, &LIBID_TestLibrary);
    }
    else if(dwReason == DLL_PROCESS_DETACH)
    {
        _Module.Term();
    }
    return TRUE;
}

STDMETHODIMP DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv)
{
#ifdef DEBUG
    printf("DllGetClassObject\n");
#endif
    return _Module.GetClassObject(rclsid, riid, ppv);
}

STDMETHODIMP DllCanUnloadNow()
{
    return (_Module.GetLockCount() == 0) ? S_OK : S_FALSE;
}

STDMETHODIMP DllRegisterServer()
{
    return _Module.RegisterServer(TRUE);
}

STDMETHODIMP DllUnregisterServer()
{
    return _Module.UnregisterServer(TRUE);
}

Test.def:

LIBRARY "Test"
EXPORTS
    DllMain             PRIVATE
    DllGetClassObject   PRIVATE
    DllCanUnloadNow     PRIVATE
    DllRegisterServer   PRIVATE
    DllUnregisterServer PRIVATE

Build and registration:

midl Test.idl
cl /LD Test.cpp Test.def oleaut32.lib
regsvr32 Test.dll

And now a host program.

server.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitializeEx(NULL, COINIT_MULTITHREADED);
    IClassFactory *pCF;
    res = CoGetClassObject(CLSID_CoTest, CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory, (void **)&pCF);
    ReturnCheck(_T("CoGetClassObject"), res);
    DWORD dwR;
    res = CoRegisterClassObject(CLSID_CoTest, pCF, CLSCTX_LOCAL_SERVER, REGCLS_SUSPENDED|REGCLS_MULTIPLEUSE, &dwR);
    ReturnCheck(_T("CoRegisterClassObject"), res);
    CoResumeClassObjects();
    printf("Press enter to exit");
    char dummy[80];
    fgets(dummy, sizeof(dummy), stdin);
    CoRevokeClassObject(dwR);
    pCF->Release();
    CoUninitialize();
    return 0;
}

Clients using header file:

TestC.c:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#define COBJMACROS
#include "Test.h"

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    COSERVERINFO csi;
    MULTI_QI mqi[1];
    ITest *pTest;
    int v, iv;
    BSTR s1, s2, s3, sv;
    CoInitialize(NULL);
    printf("COM/C:\n");
    memset(&csi, 0, sizeof(csi));
    csi.pwszName = L"\\\\ARNEPC4";
    memset(mqi, 0, sizeof(mqi));
    mqi[0].pIID = &IID_ITest;
    res = CoCreateInstanceEx(&CLSID_CoTest, NULL, CLSCTX_REMOTE_SERVER, &csi, 1, mqi);
    ReturnCheck(_T("CoCreateInstanceEx"), res);
    pTest = (ITest *)mqi[0].pItf;
    res = ITest_Add(pTest, 123, 456, &v);
    ReturnCheck(_T("Add"), res);
    printf("123 + 345 = %d\n", v);
    s1 = SysAllocString(L"ABC");
    s2 = SysAllocString(L"XYZ");
    res = ITest_Concat(pTest, s1, s2, &s3);
    ReturnCheck(_T("Concat"), res);
    printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
    SysFreeString(s1);
    SysFreeString(s2);
    SysFreeString(s3);
    ITest_get_IV(pTest, &iv);
    ITest_get_SV(pTest, &sv);
    printf("IV = %d SV = %d %ls\n", iv, SysStringLen(sv), sv);
    SysFreeString(sv);
    ITest_put_IV(pTest, 123);
    ITest_put_SV(pTest, SysAllocString(L"ABC"));
    ITest_get_IV(pTest, &iv);
    ITest_get_SV(pTest, &sv);
    printf("IV = %d SV = %d %ls\n", iv, SysStringLen(sv), sv);
    SysFreeString(sv);
    ITest_Release(pTest);
    CoUninitialize();
    return 0;
}

Build and run:

cl TestC.c Test_i.c ole32.lib oleaut32.lib
TestC

What is happening:

  1. CoCreateInstanceEx
    1. connect to remote server
    2. get one more pointers to interfaces via IID's
  2. call methods on instance
  3. call Release on instance

Clients using type library:

TestCPP2.cpp:

#include <stdio.h>
#include <stdlib.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

//#include "TestEx.h"
#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ with CoCreateInstanceEx\n");
    COSERVERINFO csi;
    memset(&csi, 0, sizeof(csi));
    csi.pwszName = L"\\\\ARNEPC4";
    MULTI_QI mqi[1];
    memset(mqi, 0, sizeof(mqi));
    mqi[0].pIID = &IID_ITest;
    res = CoCreateInstanceEx(CLSID_CoTest, NULL, CLSCTX_REMOTE_SERVER, &csi, 1, mqi);
    ReturnCheck(_T("CoCreateInstanceEx"), res);
    ITest *pTest = (ITest *)mqi[0].pItf;
    try
    {
        long int v = pTest->Add(123, 456);
        printf("123 + 456 = %d\n", v);
        BSTR s1 = SysAllocString(L"ABC");
        BSTR s2 = SysAllocString(L"XYZ");
        BSTR s3 = pTest->Concat(s1, s2);
        printf("%d %ls + %d %ls = %d %ls\n", SysStringLen(s1), s1, SysStringLen(s2), s2, SysStringLen(s3), s3);
        SysFreeString(s1);
        SysFreeString(s2);
        SysFreeString(s3);
        BSTR sv = pTest->GetSV();
        printf("IV = %d SV = %d %ls\n", pTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        pTest->PutIV(123);
        pTest->PutSV(SysAllocString(L"ABC"));
        sv = pTest->GetSV();
        printf("IV = %d SV = %d %ls\n", pTest->GetIV(), SysStringLen(sv), sv);
        SysFreeString(sv);
        pTest->Release();
    }
    catch (_com_error er)
    {
        _tprintf("%s\n", er.ErrorMessage());
    }
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP2.cpp ole32.lib oleaut32.lib
TestCPP2

TestPas.pas:

program TestPas;

{$mode delphi}{$H+}

uses
  ActiveX, TestLibrary_1_0_TLB;

var
  test : ITest;

begin
  writeln('COM/Pascal static:');
  CoInitialize(nil);
  test := CoCoTest.CreateRemote('\\ARNEPC4');
  writeln('123 + 456 = ', test.Add(123, 456));
  writeln('ABC + XYZ = ', test.Concat('ABC', 'XYZ'));
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  test.IV := 123;
  test.SV := 'ABC';
  writeln('IV = ', test.IV, ' SV = ', test.SV); 
  CoUninitialize;
end.

Build and run:

importtl Test.tlb
fpc TestLibrary_1_0_TLB.pas
fpc TestPas.pas
TestPas

What is happening:

  1. import TLB
  2. CoCreateInstanceEx
    1. connect to remote server
    2. get one more pointers to interfaces via IID's
  3. call methods on instance
  4. call Release on instance

Clients using scripting interface:

TestVBS.vbs:

WScript.Echo "COM/VBS:"
Set test = CreateObject("Test.CoTest", "\\ARNEPC4")
WScript.Echo "123 + 456 = " & CStr(test.Add(CLng(123), CLng(456)))
WScript.Echo "ABC + XYZ = " & test.Concat("ABC", "XYZ")
WScript.Echo "IV = " & CStr(test.IV) & " SV = " & test.SV
test.IV = 123
test.SV = "ABC"
WScript.Echo "IV = " & CStr(test.IV) & " SV = " & test.SV
Set test = Nothing

Build and run:

cscript TestVBS.vbs

TestCSDyn.cs:

using System;

public class MainClass
{
    public static void Main(string[] args)
    {
        Console.WriteLine("COM/C# dynamic");
        dynamic test = Activator.CreateInstance(Type.GetTypeFromProgID("Test.CoTest", @"\\ARNEPC4"));
        Console.WriteLine("123 + 456 = {0}", test.Add(123, 456));
        Console.WriteLine("ABC + XYZ = {0}", test.Concat("ABC", "XYZ"));
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
        test.IV = 123;
        test.SV = "ABC";
        Console.WriteLine("IV = {0} SV = {1}", test.IV, test.SV);
    }
}

Build and run:

csc /platform:x86 TestCSDyn.cs
TestCDDyn

Nobody will use IDispatch with C++, but it is still relevant to see the code to understand what is happening in other languages.

TestCPP5.cpp:

#include <stdio.h>

#include <windows.h>
#include <initguid.h>
#include <tchar.h>

#import "Test.tlb" no_namespace named_guids

void ReturnCheck(LPTSTR func, HRESULT res)
{
    if(res != S_OK)
    {
        TCHAR buffer[1000];
        FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, res, 0, buffer, sizeof(buffer), 0);
        _tprintf("%s: %s\n", func, buffer);
        exit(1);
    }
}

int main()
{
    HRESULT res;
    CoInitialize(NULL);
    printf("COM/C++ via IDispatch:\n");
    CLSID clsid;
    res = CLSIDFromProgID(L"Test.CoTest", &CLSID);
    ReturnCheck(_T("CLSIDFromProgID"), res);
    COSERVERINFO csi;
    memset(&csi, 0, sizeof(csi));
    csi.pwszName = L"\\\\ARNEPC4";
    MULTI_QI mqi[1];
    memset(mqi, 0, sizeof(mqi));
    mqi[0].pIID = &IID_IDispatch;
    res = CoCreateInstanceEx(clsid, NULL, CLSCTX_REMOTE_SERVER, &csi, 1, mqi);
    ReturnCheck(_T("CoCreateInstanceEx"), res);
    IDispatch *pDisp = (IDispatch *)mqi[0].pItf;
    DISPID dispid;
    LPOLESTR func;
    VARIANT args[2], retval;
    DISPPARAMS params;
    params.rgvarg = args;
    params.rgdispidNamedArgs = 0;
    params.cNamedArgs = 0;
    // Add
    params.cArgs = 2;
    func = L"Add";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_I4;
    args[0].lVal = 456;
    VariantInit(&args[1]);
    args[1].vt = VT_I4;
    args[1].lVal = 123;
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("123 + 345 = %d\n", retval.lVal);
    // Concat
    params.cArgs = 2;
    func = L"Concat";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_BSTR;
    args[0].bstrVal = SysAllocString(L"ABC");
    VariantInit(&args[1]);
    args[1].vt = VT_BSTR;
    args[1].bstrVal = SysAllocString(L"XYZ");
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("3 ABC + 3 XYZ = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    // Get
    params.cArgs = 0;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("IV = %d", retval.lVal);
    params.cArgs = 0;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf(" SV = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    // Put
    DISPID temp = DISPID_PROPERTYPUT; // these 3 lines are necessary
    params.rgdispidNamedArgs = &temp;
    params.cNamedArgs = 1;
    //
    params.cArgs = 1;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_I4;
    args[0].lVal = 123;
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &params, NULL, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    params.cArgs = 1;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&args[0]);
    args[0].vt = VT_BSTR;
    args[0].bstrVal = SysAllocString(L"ABC");
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT, &params, NULL, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    //
    params.rgdispidNamedArgs = 0;
    params.cNamedArgs = 0;
    // Get
    params.cArgs = 0;
    func = L"IV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf("IV = %d", retval.lVal);
    params.cArgs = 0;
    func = L"SV";
    res = pDisp->GetIDsOfNames(IID_NULL, &func, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    ReturnCheck(_T("IDispatch GetIDsOfNames"), res);
    VariantInit(&retval);
    res = pDisp->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYGET, &params, &retval, NULL, NULL);
    ReturnCheck(_T("IDispatch Invoke"), res);
    printf(" SV = %d %ls\n", SysStringLen(retval.bstrVal), retval.bstrVal);
    //
    pDisp->Release();
    CoUninitialize();
    return 0;
}

Build and run:

cl /EHsc TestCPP5.cpp ole32.lib oleaut32.lib
TestCPP5

TestPHP.php:

<?php
echo "COM/PHP:\r\n";
$test = new com('Test.CoTest', 'ARNEPC4'); 
echo sprintf("123 + 456 = %d\r\n", $test->Add(123, 456));
echo sprintf("ABC + XYZ = %s\r\n", $test->Concat("ABC", "XYZ"));
echo sprintf("IV = %d SV = %s\r\n", $test->IV, $test->SV);
$test->IV = 123;
$test->SV = "ABC";
echo sprintf("IV = %d SV = %s\r\n", $test->IV, $test->SV);
?>

Build and run:

php TestPHP.php

What is happening:

  1. CreateObject / CreateInstance / CoCreateInstanceEx
    1. connect to remote server
    2. get one more pointers to interfaces via IID's
  2. call methods on instance
    1. use GetIDsOfNames to translate name to method
    2. use Invoke to call method
  3. set reference to null or let reference go out of scope
    1. call Release on instance

Final comments:

Hopefully the previous sections gave some understanding of how COM works.

Several COM related technologies exist:

ActiveX
COM intended for usage by web browsers
DCOM
Distributed COM - client and server are running on different systems (similar to CORBA)
COM+
COM/DCOM using distributed transactions via MTS (Microsoft Transaction Server)
WinRT
WinRT components are COM components that implements the interface IInspectable and has meta-data in .NET format

Some other articles have examples of COM too:

Article history:

Version Date Description
1.0 August 6th 2021
(but I have been working on this since 2010!)
Initial version
1.1 May 31st 2022 Add DCOM section
1.2 June 6th 2022 Add PHP and Python examples

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj