我想将CUIAutomation
COM 类对象的方法公开给我通过我的 Active / Windows 脚本应用程序加载和运行的脚本(我没有实现脚本引擎,我正在使用一个,特别是“JScript”引擎)。脚本宿主通常能够IDispatch
自动公开任何实现对象,但CUIAutomation
类不实现IDispatch
。调用对象 return 上QueryInterface
的IDispatch
指针E_NOINTERFACE
。
我在下面详细说明的整个问题基本上归结为:是否可以为未实现的对象实现调度IDispatch
?我敢打赌,如果可能的话,拥有对象的 coclass 的类型信息将是必要的(并且可能是充分的)要求。如果可能的话,我尝试这样做有什么问题,如下所述?我的替代方案是什么?
如前所述,我的解决方案围绕着我的假设,即如果我应该拥有 coclass 的类型信息 ( ITypeInfo
) CUIAutomation
,那么理论上我应该能够对所述 coclass 的对象进行运行时调度,即使没有实现它,IDispatch
而只是通过ITypeInfo
likeGetIDsOfNames
和的方法Invoke
。实际上,我会设计一个我自己的类来实现,IDispatch
包装一个CUIAutomation
对象(或任何IUnknown
我可以与适当类型信息配对的对象)并将成员调度委托给被包装的对象。
我已经成功地加载了至少CUIAutomation
coclass 的类型信息——它都在 Windows 注册表中——通过找到实现它的模块的路径并使用以下LoadTypeLib
过程:
(注意:我有断言检查调用是否成功(通过比较S_OK
orERROR_SUCCESS
等 - 取决于成功的代码),但为了简洁起见,我省略了片段中的错误检查 - 如果未检查调用是否返回value 总是有一个断言围绕它,如所描述的)
/// 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
在相关的说明中,我需要一种方法让脚本在它们(脚本)可以调用这些对象上的方法之前获取对类对象的引用。createObject
我直接允许脚本通过在“全局”实现对象上公开方法来创建指定 CLSID 的 COM 对象IDispatch
,这很像 VBScript 的CreateObject
函数或new ActiveXObject(progID)
当时在 Internet Explorer 中的函数。它用于CoCreateInstance
创建由指定 CLSID 标识的 COM 类的对象:
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
。到目前为止,这是最直接的问题。
什么没有实施,为什么?成员 ID ( dispIdMember
) 匹配pTypeInfo->GetIDsOfNames
之前写入的内容,后者返回S_OK
,因此至少根据它,成员 ID 是有效的。我也不认为参数格式与它有任何关系——如果有的话,我会期待pTypeInfo->Invoke
调用中的另一个错误代码。
将GetTypeInfoCount
write1
作为类型信息计数并pTypeInfo
作为结果写入GetTypeInfo
对后续调用的结果没有影响ITypeInfo::Invoke
——它仍然失败。
我还尝试使用我在原始coclass对象上获得的实际IUIAutomation
接口类型信息(在下面的片段中) ,而不是coclass本身的信息,即使文档类型暗示可能会自动递归到引用类型:pTypeInfoDefaultInterface
ITypeInfo
ITypeInfo::Invoke
HREFTYPE hRefType;
pTypeInfo->GetRefTypeOfImplType(0, &hRefType);
ITypeInfo * pTypeInfoDefaultInterface;
pTypeInfo->GetRefTypeInfo(hRefType, &pTypeInfoDefaultInterface);
不管是接口还是coclass类型信息,效果都是一样的ITypeInfo::Invoke
——returns E_NOTIMPL
。
我究竟做错了什么?我是否遗漏了一些关于 COM 或调度的重要信息,或者哪些类型信息可以为我做些什么?我不编写 IDL 文件,DispatchProxy
它不是某些 COM 服务器的一部分,它是我的应用程序的严格内部类。我查看了 Visual C++ 允许我查看的虚函数表,还GetFuncDesc
对类型信息进行了一些调查——它填写的内容似乎很可靠——有一切——名称和参数类型以及计数我试图调用的每个预期方法。指针有效且可用。
我承认,至少GetRootElement
需要一个指向对象指针的指针,从甚至可能无法传递此类参数的脚本中调度此类方法,这可能是罪魁祸首。但根据文档,在这种情况下,ITypeInfo::Invoke
可能应该返回E_INVALIDARG
or 。DISP_E_EXCEPTION
我也尝试过玩弄CreateStdDispatch
,但有两件事让我感到厌烦——对于初学者来说,为什么上述方法不起作用?其次,我不明白从哪里分派什么CreateStdDispatch
以及哪些指针作为哪些参数。我想除非它是这里的惯用选择,否则这不是我的实际问题,但如果它对我的情况有所帮助,我完全愿意解释它究竟做了什么以及如何插入它。