“纯”调度接口编组

时间:2021-04-19 21:54:52

标签: com marshalling ole idispatch

更新 2021-04-20:此处提供的代码仅用于说明目的。正如 Simon Mourier 所指出的,对于这样一个简单类的编组过程中,不需要所有的 TLB 恶作剧。实际上,TLB 是由第三方提供的,有问题的接口用于回调。 然而,调用接口的对象驻留在另一个进程中,所以我确实确实必须在实现接口后对其进行编组。由于演示整个进程间流程很乏味,因此我选择了更简单的方法 - 进程内公寓间封送。


假设我有以下类型库:

import "oaidl.idl";
import "ocidl.idl";

[
    uuid(99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82),
    version(1.0),
]
library IsThisRealMarshal
{
    [
        uuid(80997EA1-0144-41EC-ABCF-5FAD08D5A498),
        nonextensible,
    ]
    dispinterface IMyInterface
    {
        properties:
        methods:
            [id(1)]
            void Method();
    };
};

我想将 IMyInterface 编组到另一间公寓。由于它是一个调度接口,我想为此使用 OLE 封送拆收器。因此,我注册了类型库:

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\SOFTWARE\Classes\TypeLib\{99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82}]

[HKEY_CURRENT_USER\SOFTWARE\Classes\TypeLib\{99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82}\1.0]

[HKEY_CURRENT_USER\SOFTWARE\Classes\TypeLib\{99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82}\1.0\0]

[HKEY_CURRENT_USER\SOFTWARE\Classes\TypeLib\{99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82}\1.0\0\win32]
@="path\\to\\library.tlb"

以及接口(将代理 CLSID 设置为 OLE 封送拆收器的 CLSID):

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\SOFTWARE\Classes\Interface\{80997EA1-0144-41EC-ABCF-5FAD08D5A498}]

[HKEY_CURRENT_USER\SOFTWARE\Classes\Interface\{80997EA1-0144-41EC-ABCF-5FAD08D5A498}\ProxyStubClsid32]
@="{00020424-0000-0000-C000-000000000046}"

[HKEY_CURRENT_USER\SOFTWARE\Classes\Interface\{80997EA1-0144-41EC-ABCF-5FAD08D5A498}\TypeLib]
@="{99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82}"
"Version"="1.0"

我尝试编组(为简洁起见省略了错误检查):

CoInitializeEx(nullptr, COINIT_MULTITHREADED);

CComPtr<IMyInterface> object {};
object.Attach(new MyObject);

CComPtr<IGlobalInterfaceTable> git {};
git.CoCreateInstance(CLSID_StdGlobalInterfaceTable, nullptr, CLSCTX_INPROC_SERVER);

DWORD cookie = 0;
git->RegisterInterfaceInGlobal(object, __uuidof(IMyInterface), &cookie);

auto thread = std::thread([cookie]
{
    CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
    
    CComPtr<IGlobalInterfaceTable> git {};
    git.CoCreateInstance(CLSID_StdGlobalInterfaceTable, nullptr, CLSCTX_INPROC_SERVER);

    CComPtr<IMyInterface> object {};
    git->GetInterfaceFromGlobal(cookie, __uuidof(IMyInterface), (void **)&object);
});
thread.join();

其中 MyObject 类实现最低限度的 COM 功能:

class MyObject : public IMyInterface
{
private:
    std::atomic<ULONG> _refcount = 1;

public:
    MyObject() = default;
    
    MyObject(MyObject const &) = delete;
    MyObject & operator=(MyObject const &) = delete;
    
    HRESULT QueryInterface(const IID& riid, void** ppvObject) override
    {
        if (nullptr == ppvObject)
        {
            return E_POINTER;
        }

        if (riid == __uuidof(IUnknown))
        {
            *ppvObject = static_cast<IUnknown *>(this);
        }
        else if (riid == __uuidof(IDispatch))
        {
            *ppvObject = static_cast<IDispatch *>(this);
        }
        else if (riid == __uuidof(IMyInterface))
        {
            *ppvObject = static_cast<IMyInterface *>(this);
        }
        else
        {
            *ppvObject = nullptr;
            return E_NOINTERFACE;
        }

        static_cast<IUnknown *>(*ppvObject)->AddRef();

        return S_OK;
    }
    
    ULONG AddRef() override
    {
        return ++_refcount;
    }
    
    ULONG Release() override
    {
        auto const new_refcount = --_refcount;
        if (0 == new_refcount)
        {
            delete this;
        }
        return new_refcount;
    }
    
    HRESULT GetTypeInfoCount(UINT* pctinfo) override
    {
        return E_NOTIMPL;
    }
    
    HRESULT GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo) override
    {
        return E_NOTIMPL;
    }
    
    HRESULT GetIDsOfNames(const IID& riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) override
    {
        return E_NOTIMPL;
    }
    
    HRESULT Invoke(DISPID dispIdMember, const IID& riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams,
        VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) override
    {
        return E_NOTIMPL;
    }
};

不幸的是,调用 GetInterfaceFromGlobal 失败并显示 E_FAIL

调试显示没有调用任何 IDispatch 方法,只有 IUnknown 方法被调用。此外,似乎 E_FAIL 源自 combase!CheckTypeInfo。首先,此函数使用 ITypeInfo::GetTypeAttr 检索有关 IMyInterface 的信息:

WinDbg display of the TYPEATTR structure for IMyInterface

然后继续检查标志 TYPEFLAG_FDUAL (0x40) 或 TYPEFLAG_FOLEAUTOMATION (0x100) 是否存在于 {{ 3}} 结构:

TYPEATTR

Disassembly of the CheckTypeInfo function, with the check for TYPEFLAG_FDUAL highlighted

由于这些标志都不存在(该字段的值是 wTypeFlags,而且 IDL 确实没有将接口标记为 0x1080[oleautomation]),因此函数失败与[dual]

我做错了什么?如果 OLE 封送拆收器确实无法封送此接口,假设我无法修改 IDL,除了自己实现 E_FAIL 之外,还有什么我可以做的吗?

1 个答案:

答案 0 :(得分:0)

在 Simon Mourier 的 code 的帮助下,我设法找到了问题所在。问题是我使用了 PSOAInterface 代理 ({00020424-0000-0000-C000-000000000046})。由于 IMyInterface 不是 OLE 自动化接口(即没有标有 [oleautomation]),这正确地失败了。

解决方案是使用 PSDispatch 代理 ({00020420-0000-0000-C000-000000000046}),它能够编组纯 IDispatch 接口。

相关问题