“纯”调度接口编组

Posted

技术标签:

【中文标题】“纯”调度接口编组【英文标题】:"Pure" dispinterface marshaling 【发布时间】:2021-07-14 02:41:20 【问题描述】:

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 封送器的):

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 的信息:

然后它会继续检查标志 TYPEFLAG_FDUAL (0x40) 或 TYPEFLAG_FOLEAUTOMATION (0x100) 是否存在于 TYPEATTR 结构的 wTypeFlags 字段中:

由于这两个标志都不存在(该字段的值为0x1080,并且IDL 确实没有将接口标记为[oleautomation][dual]),因此函数将失败并显示E_FAIL

我做错了什么?如果 OLE 编组器确实无法编组这个接口,那么除了自己实现 IMarshal 之外,我还能做些什么,假设我不能修改 IDL?

【问题讨论】:

不确定您最终要完成什么(听起来像是 XY 问题meta.stackexchange.com/questions/66377/what-is-the-xy-problem)。如果你的场景真的和你的代码一样(inprocess,一个在 MTA 中不是 coclass,不是 cocreateable 的对象,显然支持多线程),不要使用任何 .TLB,不要注册任何东西。它应该按原样工作。 @SimonMourier 你是对的,如果情况如此简单,我就不会打扰 TLB。现实更复杂,我只是想提供一个最小的例子。我已经编辑了这个问题,也许可以更好地传达这一点。 我仍然不确定您要使用此示例实现什么目标。将 HKEY_CURRENT_USER 更改为 [HKEY_CLASSES_ROOT,它会起作用。您的 MyObject 不是 coclass,不确定 GIT 是如何看待它的。只需用 ATL 创建一个项目,添加一个简单的 ATL 对象,通过像你这样的 dispinterface 更改创建的接口,它也可以工作。 对我来说很好:1drv.ms/u/s!AsmmEDGvydk2njUuR2POsleKAK0B?e=LG9cpx 当然要确保你构建了两个项目 :-)。在构建结束时,ATLProject 应该在 HKCU (i.imgur.com/axnzPmB.png) 中注册自己。 PS:你的代码在我的电脑上确实返回了 E_FAIL。 【参考方案1】:

在 Simon Mourier 的code 的帮助下,我设法找到了问题所在。问题是我使用了PSOAInterface 代理(00020424-0000-0000-C000-000000000046)。由于 IMyInterface 不是 OLE 自动化接口(即未标记为 [oleautomation]),因此失败了。

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

【讨论】:

以上是关于“纯”调度接口编组的主要内容,如果未能解决你的问题,请参考以下文章

将 COM 接口编组到多个线程时,克隆流是不是足够,还是需要复制?

如何手动将 .NET 对象编组为双 COM 接口?

通过非默认 AppDomain 中的 C# 函数调用编组 C++ 指针接口

为啥 Windows 10 UWP 应用程序由于调用了为不同线程编组的接口而崩溃?

创建 COM 接口,返回一个在 C# 中编组为 IntPtr 的指针

该应用程序调用了一个为不同线程编组的接口 - Xamarin Forms