Как добавить диспетчеризацию для COM-объекта, который не реализует интерфейс IDispatch?

Я хочу раскрыть методы CUIAutomation Объекты класса COM для сценариев, которые я загружаю и запускаю в моем приложении Active / Windows Script (я не реализую механизм сценариев, я использую его, в частности, механизм "JScript"). Хост скрипта обычно может открыть любойIDispatch-реализация объекта автоматически, но CUIAutomationкласс не реализуетIDispatch. Звонки вQueryInterface для IDispatch указатель на объект return E_NOINTERFACE.

Весь мой вопрос, о котором я подробно расскажу ниже, в основном сводится к следующему: можно ли реализовать диспетчеризацию для объекта, который не реализует IDispatch? Бьюсь об заклад, наличие информации о типе для кокласса объекта было бы необходимым (и, возможно, достаточным) требованием, если бы это было возможно. Если это возможно, что не так в моей попытке сделать это, как описано ниже? Какие у меня есть альтернативы?

Как уже упоминалось, мое решение основано на моей гипотезе о том, что если мне нужна информация о типе (ITypeInfo) за CUIAutomation кокласс, то теоретически я должен иметь возможность выполнять диспетчеризацию во время выполнения для объектов указанного кокласса, даже если он не реализует IDispatch но только с помощью методов ITypeInfo нравиться GetIDsOfNames а также Invoke. Практически я бы разработал собственный класс, который реализуетIDispatch, оборачивает CUIAutomation объект (или любой IUnknownв этом отношении я могу использовать правильную информацию о типе) и делегирует отправку члена обернутому объекту.

Мне удалось загрузить информацию о типе по крайней мере для CUIAutomation coclass - это все в реестре Windows - путем поиска пути к модулю, который его реализует, и использования LoadTypeLib процедура:

(Примечание: у меня есть утверждения, которые проверяют успешность вызовов (по сравнению с S_OK или ERROR_SUCCESS и т. д. - зависит от того, какой код для успеха), но я опускаю указанную проверку ошибок в фрагментах для краткости - если вызов не проверяется на возвращаемое значение, вокруг него всегда есть утверждение, как описано)

/// Return zero if and only if successful
int LoadTypeInfo(LPOLESTR szCLSID, ITypeInfo * * ppTypeInfo) {
    HKEY hRegKeyCLSIDs;
    RegOpenKeyEx(HKEY_CLASSES_ROOT, "CLSID", 0, KEY_READ, &hRegKeyCLSIDs); /// Only need to do this once through application lifetime, but here for context
    HKEY hRegKeyCLSID;
    RegOpenKeyEx(hRegKeyCLSIDs, szCLSID , 0, KEY_READ, &hRegKeyCLSID);
    BYTE data[MAX_PATH];
    DWORD cbData = sizeof(data);
    RegGetValueW(hRegKeyCLSID, L"InprocServer32", NULL, RRF_RT_REG_SZ, NULL, data, &cbData);
    ITypeLib * pTypeLib;
    LoadTypeLib((LPOLESTR)data, &pTypeLib);
    return (pTypeLib->GetTypeInfoOfGuid(CLSID, ppTypeInfo) == S_OK);
}

Делегирование DispatchProxy класс оформлен следующим образом:

class DispatchProxy: public IDispatch {
private:
    IUnknown * pUnknown;
    ITypeInfo * pTypeInfo;
public:
    DispatchProxy(IUnknown * pUnknown, ITypeInfo * pTypeInfo): pUnknown(pUnknown), pTypeInfo(pTypeInfo) {
        /// `pUnknown` is the object that doesn't implement `IDispatch` and `pTypeInfo` is the type information for objects like what `pUnknown` points to.
    }
    /// Omitting `AddRef` and `Release` -- these are rather standard.
    HRESULT STDMETHODCALLTYPE DispatchProxy::QueryInterface(REFIID riid, void * * ppvObject) {
        if(ppvObject == nullptr) {
            return E_POINTER;
        }
        else
        if(riid == IID_IUnknown || riid == IID_IDispatch) {
            *ppvObject = this;
            ((IUnknown *)*ppvObject)->AddRef();
            return S_OK;
        }
        else {
            *ppvObject = NULL;
            return E_NOINTERFACE;
        }
    }
    /// NOT returning any type information -- explanation below, if you're surprised
    HRESULT STDMETHODCALLTYPE DispatchProxy::GetTypeInfoCount(UINT * pctinfo) {
        *pctinfo = 0;
        return S_OK;
    }
    HRESULT STDMETHODCALLTYPE DispatchProxy::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo ** ppTInfo) {
        if(iTInfo != 0) return DISP_E_BADINDEX;
        _ASSERTE(*ppTInfo == NULL);
        return E_NOTIMPL; /// Even though type information for the object being delegated to, is available, obviously, I am unsure whether it technically is valid for `DispatchProxy`, which may have a completely different, incompatible, layout. Granted, `E_NOTIMPL` isn't part of the contract for this method, but like I said -- I am unsure about this one.
    }
    HRESULT STDMETHODCALLTYPE DispatchProxy::GetIDsOfNames(REFIID riid, LPOLESTR * rgszNames, UINT cNames, LCID lcid, DISPID * rgDispId) {
        return pTypeInfo->GetIDsOfNames(rgszNames, cNames, rgDispId); /// Returns S_OK, all good. Also tried `DispGetIDsOfNames(pTypeInfo, rgszNames, cNames, rgDispId)` with same result
    }
    HRESULT STDMETHODCALLTYPE DispatchProxy::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS * pDispParams, VARIANT * pVarResult, EXCEPINFO * pExcepInfo, UINT * puArgErr) {
        return pTypeInfo->Invoke(pUnknown, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); /// Fails with `E_NOTIMPL`. Also tried `DispInvoke(pUnknown, pTypeInfo, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr)` with same result
    }
};

В связи с этим мне нужно, чтобы сценарий получал ссылки на такие объекты, как объект CUIAutomationclass, прежде чем они (скрипты) смогут вызывать методы для них. Я прямо разрешаю сценариям создавать COM-объекты указанного CLSID, выставляяcreateObject метод на "глобальном" IDispatch-реализация объекта, как в VBScript CreateObject функция или new ActiveXObject(progID)в Internet Explorer когда-то. Оно используетCoCreateInstance для создания объекта класса COM, идентифицированного указанным CLSID:

HRESULT Global::CreateObject(VARIANT * pvCLSID, VARIANT * pvResult) {
    _ASSERTE(V_VT(pvCLSID) == VT_BSTR);
    CLSID CLSID;
    CLSIDFromString(V_BSTR(pvCLSID), &CLSID);
    IUnknown * pUnknown;
    CoCreateInstance(CLSID, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, &pUnknown);
    IDispatch * pDispatch;
    HRESULT hResult = pUnknown->QueryInterface(&pDispatch);
    if(hResult != S_OK) {
        _ASSERTE(hResult == E_NOINTERFACE);
        ITypeInfo * pTypeInfo;
        if(LoadTypeInfo(V_BST(pvCLSID), &pTypeInfo)) { /// No type information was available -- not much choice but to return the created object as `IUnknown`
            V_VT(pvResult) = VT_UNKNOWN;
            V_UNKNOWN(pvResult) = pUnknown;
            return S_OK;
        } else {
            pDispatch = new DispatchProxy(pUnknown, pTypeInfo);
        }
    }
    if(pvResult) {
        V_VT(pvResult) = VT_DISPATCH;
        V_DISPATCH(pvResult) = pDispatch;
    }
    return S_OK;
}

Скрипт может создать CUIAutomation объект и получите ссылку на новый DispatchProxy оборачивая его так:

uiautomation = createObject("{ff48dba4-60ef-4201-aa87-54103eef594e}");

Затем он должен иметь возможность вызывать методы (здесь GetRootElement) на объекте:

uiautomation.GetRootElement(/* parameters */);

К сожалению, pTypeInfo->Invoke вызов в основе всего этого возвращается E_NOTIMPL. На данный момент это неотложная проблема.

Что не реализовано и почему? Идентификатор участника (dispIdMember) соответствует тому, что pTypeInfo->GetIDsOfNames пишет раньше, а последний возвращает S_OK, так что идентификатор участника, по крайней мере, в соответствии с ним, действителен. Я не думаю, что формат параметра имеет к этому какое-то отношение - я бы ожидал другого кода ошибки отpTypeInfo->Invoke позвоните, если это так.

Изготовление GetTypeInfoCount записывать 1 как количество информации о типе и запись pTypeInfo как результат GetTypeInfo не влияет на результат последующих ITypeInfo::Invoke звоните - все равно не получается.

Я также пробовал использовать настоящий IUIAutomation информация о типе интерфейса (pTypeInfoDefaultInterfaceв фрагменте ниже), который я получаю в исходном коклассе ITypeInfoобъект, в отличие от самого кокласса, хотя документация подразумеваетITypeInfo::Invoke может автоматически переходить к ссылочным типам:

HREFTYPE hRefType;
pTypeInfo->GetRefTypeOfImplType(0, &hRefType);
ITypeInfo * pTypeInfoDefaultInterface;
pTypeInfo->GetRefTypeInfo(hRefType, &pTypeInfoDefaultInterface); 

Эффект один и тот же, независимо от того, используется ли интерфейс или информация о типе кокласса - ITypeInfo::Invoke возвращается E_NOTIMPL.

Что я делаю неправильно? Я упускаю какую-то важную информацию о COM, отправке или о том, какой тип информации может мне помочь? Я не пишу файлы IDL, иDispatchProxyне является частью какого-то COM-сервера, это строго внутренний класс моего приложения. Я посмотрел на таблицы виртуальных функций, которые Visual C++ позволяет мне заглянуть, а также провел небольшое исследование сGetFuncDescв информации о типе - то, что она заполняет, кажется твердым - есть все - имена и тип параметра и количество для каждого ожидаемого метода, который я пытаюсь вызвать. Указатели действительны и доступны.

Я признаю, что по крайней мере с GetRootElement который ожидает указатель на указатель на объект, отправляя такой метод из сценария, который может даже не иметь возможности передавать параметры такого типа, может быть виновником. Но согласно документации,ITypeInfo::Invoke вероятно должен вернуться E_INVALIDARG или DISP_E_EXCEPTION, в таком случае.

Я пробовал поиграть с CreateStdDispatch тоже, но меня раздражают две вещи - для начала, почему все вышеперечисленное не должно работать? А во-вторых, я не понимаю, какие именно рассылки откуда сCreateStdDispatchи какие указатели используются в качестве аргументов. Я полагаю, если это идиоматическая альтернатива здесь, это не мой настоящий вопрос, но если это поможет в моем случае, я все за то, чтобы получить объяснение о том, что именно он делает и как его подключить.

0 ответов

Другие вопросы по тегам