Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement COM IDispatch without ATL

Tags:

c++

c

qt

com

I am writing an Excel RTD server implementation and I'm stuck on the boilerplate for a coclass which implements IDispatch. I have no access to ATL, but I am using ActiveQt, although I'm interested in how to do this in raw C or C++ too. How to properly implement the IDispatch methods in a COM server?

The documentation is just panickingly awful, as always. What I have so far read:

  • It is better practice to delegate the IDispatch method calls to some ITypeInfo. Is this correct?
  • If so, how to get an ITypeInfo to myself? LoadTypeLib() and family (followed by looking at ITypeLib::GetTypeInfo())?
  • If not, how is it implemented properly? Links to good-quality documentation and self-contained examples are of much use.

The LoadTypeLib() approach seems appropriate for a COM client to reach type information for some library, not for a COM server trying to introspect itself. Am I correct?

like image 332
alecov Avatar asked Oct 08 '13 16:10

alecov


2 Answers

Implementing IDispatch can be easy or hard. (Assuming you cannot use ATL).

The easy way is to not support TypeInfo (return 0 from GetTypeInfoCount and E_NOTIMPL from GetTypeInfo. Nobody should call it.).

Then all you have to support is GetIDsOfNames and Invoke. It's just a big lookup table essentially.

For GetIDsOfNames, return DISP_E_UNKNOWNNAME if cNames != 1. You aren't going to support argument names. Then you just have to lookup rgszNames[0] in your mapping of names-to-ids.

Finally, implement Invoke. Ignore everything except pDispParams and pVarResult. Use VariantChangeType to coerce the parameters to the types you expect, and pass to your implementation. Set the return value and return. Done.

The hard way is to use ITypeInfo and all that. I've never done it and wouldn't. ATL makes it easy so just use ATL.

If picking the hard way, Good luck.

like image 159
Ben Avatar answered Oct 13 '22 14:10

Ben


If the interface is properly defined in the IDL and compiled into a type library, implementing IDispatch via the type library's ITypeInfo is quite feasible as it's mostly delegating. The interesting part is ITypeInfo::Invoke which relies upon correct C++ v-table layout:

public class CComClass: public IDualInterface
{
    // ...

    // implementing IDualInterface

    ITypeInfo* m_pTypeInfo // can be obtained via ITypeLib::GetTypeInfoOfGuid for the GUID of IDualInterface

    // IDispatch
    STDMETHODIMP GetTypeInfoCount(UINT* pctinfo)
    {
        *pctinfo = 1;
        return S_OK;
    }

    STDMETHODIMP GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo** pptinfo)
    {
        if (0 != itinfo)
            return E_INVALIDARG;
        (*pptinfo = m_pTypeInfo)->AddRef();
        return S_OK;
    }

    STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgdispid)
    {
        return m_pTypeInfo->GetIDsOfNames(rgszNames, cNames, rgdispid);
    }

    STDMETHODIMP Invoke(DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pdispparams, VARIANT* pvarResult, EXCEPINFO* pexcepinfo, UINT* puArgErr)
    {
        return m_pTypeInfo->Invoke(static_cast<IDualInterface*>(this), dispidMember, wFlags, pdispparams, pvarResult, pexcepinfo, puArgErr); 
    }
}

I've used a similar approach to create a script-callable wrapper for MSHTML DOM objects to bypass scripting security restrictions.

So where do you get the ITypeInfo from? Essentially you get it by:

  1. Write an IDL file which declares your interface as a dual interface. It has to be a dual interface, as that is how the ITypeInfo implementation knows which function to invoke - it cannot just invoke the C++ functions directly on your class because C++ has no reflection and because it is language neutral. Therefore it can only delegate the Invoke call to another method declared in the type library.
  2. Compile the IDL to a header file and type library as part of the build process
  3. The header file produced from the IDL defines the interface, which your implementing class must inherit from. Once you have implemented all the methods you are good to go. (For development start by making them all return E_NOTIMPL then implement them one by one)
  4. Install the Type Library, either to the destination directory, or as a resource in the EXE/DLL. It will need to be registered by calling RegisterTypeLib. If it is embedded as a resource, you should call this from your DllRegisterServer implementation.
  5. Load the type library when the first instance of your object is created, using LoadTypeLib. This gives you an ITypeLib
  6. Get the ITypeInfo you need using GetTypeInfoOfGuid.
like image 32
noseratio Avatar answered Oct 13 '22 13:10

noseratio