Как добавить диспетчеризацию для 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
}
};
В связи с этим мне нужно, чтобы сценарий получал ссылки на такие объекты, как объект CUIAutomation
class, прежде чем они (скрипты) смогут вызывать методы для них. Я прямо разрешаю сценариям создавать 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
и какие указатели используются в качестве аргументов. Я полагаю, если это идиоматическая альтернатива здесь, это не мой настоящий вопрос, но если это поможет в моем случае, я все за то, чтобы получить объяснение о том, что именно он делает и как его подключить.