Implement IDispatch-only interface easily in C++
03:33 29 May 2025

I defined the following IDispatch-only interface in my C# project:

    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    [Guid("3532C4E8-D320-487C-8BD4-F448B94B9E83")]
    public interface ICppRuntimeApi2
    {
        [DispId(1)]
        bool TestCallback(string pasteType);
    }

After importing the generated Type Library in my C++ project, I can implement my ICppRuntimeApi2 freely, like for example:

class CppRuntimeApiImpl2 : public MyProject::ICppRuntimeApi2 {
public:
    CppRuntimeApiImpl2() : refCount(1) {}

    // IUnknown methods
    HRESULT __stdcall QueryInterface(REFIID riid, void** ppv) override {
        if (riid == IID_IUnknown || riid == IID_IDispatch || riid == __uuidof(MyProject::ICppRuntimeApi2)) {
            *ppv = static_cast(this);
        }
        else {
            *ppv = nullptr;
            return E_NOINTERFACE;
        }
        AddRef();
        return S_OK;
    }

    ULONG __stdcall AddRef() override {
        return InterlockedIncrement(&refCount);
    }

    ULONG __stdcall Release() override {
        ULONG count = InterlockedDecrement(&refCount);
        if (count == 0) {
            delete this;
        }
        return count;
    }

    // IDispatch methods
    HRESULT __stdcall GetTypeInfoCount(UINT* pctinfo) override {
        *pctinfo = 0;
        return S_OK;
    }

    HRESULT __stdcall GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) override {
        *ppTInfo = nullptr;
        return E_NOTIMPL;
    }

    HRESULT __stdcall GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) override {
        if (cNames != 1) return DISP_E_UNKNOWNNAME;
        if (_wcsicmp(rgszNames[0], L"TestCallback") == 0) {
            rgDispId[0] = 1;
            return S_OK;
        }
        return DISP_E_UNKNOWNNAME;
    }
    

    HRESULT __stdcall Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) override {
        if (dispIdMember == 1) {
            if (pDispParams->cArgs != 1 || pDispParams->rgvarg[0].vt != VT_BSTR) {
                return DISP_E_BADPARAMCOUNT;
            }
            std::wstring pasteType(pDispParams->rgvarg[0].bstrVal, SysStringLen(pDispParams->rgvarg[0].bstrVal));
            bool result = TestCallback(pasteType);
            if (pVarResult) {
                pVarResult->vt = VT_BOOL;
                pVarResult->boolVal = result ? VARIANT_TRUE : VARIANT_FALSE;
            }
            return S_OK;
        }
        return DISP_E_MEMBERNOTFOUND;
    }

    // ICppRuntimeApi2 method
    bool TestCallback(const std::wstring& pasteType) {
        MessageBoxW(nullptr, (L"TestCallback call via COM interop! \nTag: " + pasteType).c_str(), L"TestCallback", MB_OK);
        return true;
    }

private:
    LONG refCount;
};

This solution works, but obviously this isn't a viable solution. It would require for new methods extensive manual effort to implement each DispID check, parameters type check, and the call to the actual logic C++ side.

Later I found that using ATL it is possible to implement the IDispEventImpl class, then use the SINK_ENTRY macros to "register" the methods without typing the checks manually, but I can't make it work. Calling the method TestCallback from C# it doesn't seem to call anything. I tried to call the Advise functions, but still is not working.

Anyway, to arrive at least at this point ATL needed module initialization, and lots of boilerplate code which I think is a little bit overkill for my needs.

Is there a more lightweight solution other than using ATL to implement an IDispatch-only interface in C++ but in a more "automatic" and less manual way?

c# c++ com com-interop atl