1

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<MyProject::ICppRuntimeApi2*>(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?

7
  • 1
    IDispEventImpl is for events (IConnectionPoint etc.), so it's another subject. You're looking for IDispatchImpl instead. See learn.microsoft.com/en-us/cpp/atl/… you don't have to implement IUnknown, etc. with ATL. However, you'll have some work to do. The easiest would be to do the reverse: use ATL templates to define interfaces, coclasses (so with a TLB) and use its facilities. Then either redefine the interface in C# like you do or reference the TLB built with ATL. Commented May 29 at 8:11
  • There is no really easy way, expect a little learning curve and learn about ATL Commented May 29 at 9:26
  • @SimonMourier I took a look at and tried to implement the IDispatchImpl class, but it seems it is made for dual interfaces... my ICppRuntimeApi2 is an IDispatch-only interface, actually. In fact, when calling my callback after implementing IDispatchImpl, the Invoke method gets called, but it does nothing as predicted (following the Invoke call inside ATL/COM sources it returns with the error DispID not found, when calling m_pInfo->Invoke). Commented May 29 at 13:52
  • There isn't any mapping between DispIDs of the functions and the actual functions' implementations by implementing IDispatchImpl alone for an IDispatch-only interface. Commented May 29 at 13:53
  • That's why it's easier with a dual interface and a full TLB, because dispids and functions are implicitely related. For IDispatch-only you'll get no help. Originally, IDispatch interfaces were meant for high-level languages (like VB/VBA, scripting, VBScript, JScript, etc), and are not easy to use from C++. If you want to use ATL, you're better off using dual interface. Commented May 29 at 14:58

1 Answer 1

1

IDispEventImpl is what the ATL has for implementing a pure dispinterface. It's meant for event sinks, but it can be adapted for a standalone object. The trick it plays is, it implements its own COM identity (roughly, IUnknown::QueryInterface implementation) distinct from that of the derived class. This is to allow e.g. a COM control that exposes its own interfaces, but also handles events from multiple different event sources; so it needs to implement N+1 different sets of interfaces.

The way to make use of it as a "primary" COM identity is to delegate to it in your interface map. Something like this (not tested):

class CMyObject;

// Replace ellipsis with your IID and LIBID.
using MyDispEventImpl = IDispEventImpl<1, CMyObject, ...>;

class CMyObject : public CComObjectRoot, public MyDispEventImpl {
 public:
  BEGIN_COM_MAP(CMyObject)
    COM_INTERFACE_ENTRY_IID(IID_IUnknown, MyDispEventImpl)
    COM_INTERFACE_ENTRY_FUNC_BLIND(0, Forward)
  END_COM_MAP()

  static HRESULT WINAPI Forward(void* pv, REFIID riid, LPVOID* ppv, DWORD_PTR) {
    return ((IUnknown*)(MyDispEventImpl*)(CMyObject*)pv)->
        QueryInterface(riid, ppv);
  }

  BEGIN_SINK_MAP(CMyObject)
  // Sink map entries to taste. Use 1 for the `id` parameter, the same value
  // used in `MyDispEventImpl` typedef.
  END_SINK_MAP()
};
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.