Внедрить COM IDispatch без ATL

Я пишу реализацию Excel RTD-сервера, и я застрял на шаблоне для класса, который реализует IDispatch. У меня нет доступа к ATL, но я использую ActiveQt, хотя мне интересно, как это сделать в сыром C или С++. Как правильно реализовать методы IDispatch на COM-сервере?

Документация просто панически ужасна, как всегда. Что я до сих пор читал:

  • Лучше использовать делегировать вызов метода IDispatch для некоторых ITypeInfo. Правильно ли это?
  • Если да, как получить ITypeInfo для себя? LoadTypeLib() и семейство (с последующим просмотром ITypeLib::GetTypeInfo())?
  • Если нет, то как он выполняется правильно? Ссылки на документацию хорошего качества и самодостаточные примеры очень полезны.

Подход LoadTypeLib() кажется подходящим для COM-клиента для получения информации о типе для некоторой библиотеки, а не для COM-сервера, который пытается сам себя разобраться. Правильно ли я?

Ответ 1

Если интерфейс правильно определен в IDL и скомпилирован в библиотеку типов, реализация IDispatch через библиотеку типов ITypeInfo вполне осуществима, поскольку она в основном делегирует. Интересная часть ITypeInfo::Invoke, которая основывается на правильной компоновке V-таблицы С++:

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); 
    }
}

Я использовал аналогичный подход для создания a script -callable wrapper для объектов DOM MSHTML, чтобы обойти ограничения безопасности сценариев.

Итак, откуда вы получаете ITypeInfo? По существу вы получаете его:

  • Напишите файл IDL, который объявит ваш интерфейс как dual. Это должен быть двойной интерфейс, так как реализация ITypeInfo знает, какую функцию вызывать - она ​​не может просто вызвать функции С++ непосредственно на вашем классе, потому что С++ не имеет никакого отражения и потому что он является нейтральным языком. Поэтому он может делегировать вызов Invoke другому методу, объявленному в библиотеке типов.
  • Скомпилируйте IDL в файл заголовка и введите библиотеку как часть процесса сборки
  • Заголовочный файл, созданный из IDL, определяет интерфейс, на который должен наследоваться ваш класс реализации. После того, как вы внедрили все методы, вам хорошо идти. (Для разработки начинайте с того, что все они возвращаются E_NOTIMPL, затем реализуют их один за другим)
  • Установите библиотеку типов либо в целевой каталог, либо в качестве ресурса в EXE/DLL. Его нужно зарегистрировать, позвонив RegisterTypeLib. Если он встроен в качестве ресурса, вы должны вызвать его из своей реализации DllRegisterServer.
  • Загрузите библиотеку типов, когда создается первый экземпляр вашего объекта, используя LoadTypeLib. Это дает вам ITypeLib
  • Получите ITypeInfo, который вам нужен, используя GetTypeInfoOfGuid.

Ответ 2

Реализация IDispatch может быть простой или сложной. (Предполагая, что вы не можете использовать ATL).

Самый простой способ - не поддерживать TypeInfo (вернуть 0 из GetTypeInfoCount и E_NOTIMPL из GetTypeInfo. Никто не должен называть его.).

Тогда вам нужно поддерживать GetIDsOfNames и Invoke. Это просто большая таблица поиска.

Для GetIDsOfNames верните DISP_E_UNKNOWNNAME, если cNames != 1. Вы не собираетесь поддерживать имена аргументов. Тогда вам просто нужно искать rgszNames[0] при сопоставлении имен с именами.

Наконец, выполните Invoke. Игнорируйте все, кроме pDispParams и pVarResult. Используйте VariantChangeType для принуждения параметров к типам, которые вы ожидаете, и перейдите к своей реализации. Установите возвращаемое значение и возврат. Готово.

Трудным способом является использование ITypeInfo и все такое. Я никогда этого не делал и не хотел. ATL упрощает работу, поэтому просто используйте ATL.

Если вы выбираете трудный путь, удачи.

Ответ 3

Что вы можете сделать, это использовать Библиотеку типов.

Если у вас есть одно, это одна вещь, которую вам не придется делать. Если у вас его нет, вы можете создать его с помощью компилятора MIDL. Это бесплатный инструмент, который поставляется с Platform SDK. Конечно, в этом случае это будет означать, что вам придется писать IDL файл (который может быть большой работой, но вам нужно только определить, что вы хотите). В зависимости от типа объекта COM, на который вы нацеливаетесь, в SDK уже может быть доступен IDL файл. Когда у вас будет IDL, вы можете скомпилировать его и вернуть файл TLB.

Как только у вас есть этот файл TLB, его можно загрузить с помощью функции LoadTypeLib. Если у вас есть ссылка ITypeLib, вы можете загрузить ITypeInfo (что может быть несколько раз) и в основном маршрутизировать вызовы IDispatch (GetIDsOfNames и т.д.) в вызовы реализации ITypeInfo, поскольку они очень похожи.