0

我想将CUIAutomationCOM 类对象的方法公开给我通过我的 Active / Windows 脚本应用程序加载和运行的脚本(我没有实现脚本引擎,我正在使用一个,特别是“JScript”引擎)。脚本宿主通常能够IDispatch自动公开任何实现对象,但CUIAutomation不实现IDispatch。调用对象 return 上QueryInterfaceIDispatch指针E_NOINTERFACE

我在下面详细说明的整个问题基本上归结为:是否可以为未实现的对象实现调度IDispatch?我敢打赌,如果可能的话,拥有对象的 coclass 的类型信息将是必要的(并且可能是充分的)要求。如果可能的话,我尝试这样做有什么问题,如下所述?我的替代方案是什么?

如前所述,我的解决方案围绕着我的假设,即如果我应该拥有 coclass 的类型信息 ( ITypeInfo) CUIAutomation,那么理论上我应该能够对所述 coclass 的对象进行运行时调度,即使没有实现它,IDispatch而只是通过ITypeInfolikeGetIDsOfNames和的方法Invoke。实际上,我会设计一个我自己的类来实现IDispatch包装一个CUIAutomation对象(或任何IUnknown我可以与适当类型信息配对的对象)并将成员调度委托给被包装的对象。

我已经成功地加载了至少CUIAutomationcoclass 的类型信息——它都在 Windows 注册表中——通过找到实现它的模块的路径并使用以下LoadTypeLib过程:

(注意:我有断言检查调用是否成功(通过比较S_OKorERROR_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调用中的另一个错误代码。

GetTypeInfoCountwrite1作为类型信息计数并pTypeInfo作为结果写入GetTypeInfo对后续调用的结果没有影响ITypeInfo::Invoke——它仍然失败。

我还尝试使用我在原始coclass对象上获得的实际IUIAutomation 接口类型信息(在下面的片段中) ,而不是coclass本身的信息,即使文档类型暗示可能会自动递归到引用类型:pTypeInfoDefaultInterface ITypeInfoITypeInfo::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_INVALIDARGor 。DISP_E_EXCEPTION

我也尝试过玩弄CreateStdDispatch,但有两件事让我感到厌烦——对于初学者来说,为什么上述方法不起作用?其次,我不明白从哪里分派什么CreateStdDispatch以及哪些指针作为哪些参数。我想除非它是这里的惯用选择,否则这不是我的实际问题,但如果它对我的情况有所帮助,我完全愿意解释它究竟做了什么以及如何插入它。

4

0 回答 0