“纯”调度接口编组
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 接口编组到多个线程时,克隆流是不是足够,还是需要复制?
通过非默认 AppDomain 中的 C# 函数调用编组 C++ 指针接口
为啥 Windows 10 UWP 应用程序由于调用了为不同线程编组的接口而崩溃?