"Pure" 调度接口编组
"Pure" dispinterface marshaling
更新 2021-04-20:此处提供的代码仅用于说明目的。正如 所指出的,对于如此简单的 class 的编组过程,不需要所有的 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}.0]
[HKEY_CURRENT_USER\SOFTWARE\Classes\TypeLib\{99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82}.0[=12=]]
[HKEY_CURRENT_USER\SOFTWARE\Classes\TypeLib\{99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82}.0[=12=]\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
class 实现了最低限度的 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
个方法中的 none 个,仅调用了 IUnknown
个方法。此外,E_FAIL
似乎源自 combase!CheckTypeInfo
。首先,此函数使用 ITypeInfo::GetTypeAttr
检索有关 IMyInterface
:
的信息
然后继续检查标志 TYPEFLAG_FDUAL
(0x40
) or TYPEFLAG_FOLEAUTOMATION
(0x100
) are present in the wTypeFlags
field of the TYPEATTR
结构是否:
由于这些标志都不存在(该字段的值为 0x1080
,实际上 IDL 并未将接口标记为 [oleautomation]
或 [dual]
),函数失败 E_FAIL
.
我做错了什么?如果 OLE 封送拆收器确实无法封送此接口,假设我无法修改 IDL,除了自己实现 IMarshal
之外,我还能做些什么吗?
在 Simon Mourier 的 的帮助下,我设法找到了问题所在。问题是我使用了 PSOAInterface
代理 ({00020424-0000-0000-C000-000000000046}
)。由于 IMyInterface
不是 OLE 自动化接口(即未标记为 [oleautomation]
),这理所当然地失败了。
解决方案是使用 PSDispatch
代理 ({00020420-0000-0000-C000-000000000046}
),它能够编组纯 IDispatch
接口。
更新 2021-04-20:此处提供的代码仅用于说明目的。正如
假设我有以下类型库:
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}.0]
[HKEY_CURRENT_USER\SOFTWARE\Classes\TypeLib\{99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82}.0[=12=]]
[HKEY_CURRENT_USER\SOFTWARE\Classes\TypeLib\{99CF9EB9-9B6E-4D44-B73C-6BB8FCD45B82}.0[=12=]\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
class 实现了最低限度的 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
个方法中的 none 个,仅调用了 IUnknown
个方法。此外,E_FAIL
似乎源自 combase!CheckTypeInfo
。首先,此函数使用 ITypeInfo::GetTypeAttr
检索有关 IMyInterface
:
然后继续检查标志 TYPEFLAG_FDUAL
(0x40
) or TYPEFLAG_FOLEAUTOMATION
(0x100
) are present in the wTypeFlags
field of the TYPEATTR
结构是否:
由于这些标志都不存在(该字段的值为 0x1080
,实际上 IDL 并未将接口标记为 [oleautomation]
或 [dual]
),函数失败 E_FAIL
.
我做错了什么?如果 OLE 封送拆收器确实无法封送此接口,假设我无法修改 IDL,除了自己实现 IMarshal
之外,我还能做些什么吗?
在 Simon Mourier 的 PSOAInterface
代理 ({00020424-0000-0000-C000-000000000046}
)。由于 IMyInterface
不是 OLE 自动化接口(即未标记为 [oleautomation]
),这理所当然地失败了。
解决方案是使用 PSDispatch
代理 ({00020420-0000-0000-C000-000000000046}
),它能够编组纯 IDispatch
接口。