我试图将接口编组到另一个线程。
Windows提供方便的CoMarshalInterThreadInterfaceInStream
辅助函数来处理与直接使用CoMarshalInterface
相关联的样板代码。
const Guid CLSID_Widget = "{F8383852-FCD3-11d1-A6B9-006097DF5BD4}";
const Guid IID_IWidget = "{EBBC7C04-315E-11D2-B62F-006097DF5BD4}";
//Create our widget
HRESULT hr = CoCreateInstance(CLSID_Widget, null,
CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
IID_IWidget, out widget);
OleCheck(hr);
//Marshall the interface into an IStream
IStream stm;
hr = CoMarshalInterThreadInterfaceInStream(IID_IWidget, widget, out stm);
OleCheck(hr);
除了对CoMarshalThreadInterfaceInStream
的调用失败时:
REGDB_E_IIDNOTREG (0x80040155)
Interface not registered
COM API函数 CoMarshalInterThreadInterfaceInStream 提供CreateStreamOnHGlobal
和CoMarshalInterface
,as shown here的简单包装:
// from OLE32.DLL (approx.)
HRESULT CoMarsha1InterThreadInterfaceInStream(
REFIID riid, IUnknown *pItf, IStream **ppStm)
{
HRESULT hr = CreateStreamOnHGlobal(0, TRUE, ppStm);
if (SUCCEEDED(hr))
hr = CoMarshalInterface(*ppStm, riid, pItf,
MSHCTX_INPROC, 0, MSHLFLAGS_NORMAL);
return hr;
}
所以我们可以尝试自己。
IStream stm = new Stream()
hr = CoMarshallInterface(stm, IID_IWidget, widget,
MSHCTX_INPROC, // destination context is in-process/same host
NULL, // reserved, must be null
MSHLFLAGS_NORMAL // marshal once, unmarshal once
);
OleCheck(hr);
但是失败了:
REGDB_E_IIDNOTREG (0x80040155)
Interface not registered
我的班级不实施IMarhsal
界面。这是正确和正常的。
默认情况下,首次在对象上调用
CoMarshalInterface
时,会询问对象是否希望处理自己的跨公寓通信。此问题以QueryInterface
IMarshal
接口的IMarshal
请求的形式出现。大多数对象没有实现QueryInterface
接口并且失败了IMarshal
请求,表示他们非常乐意让COM 通过ORPC调用处理所有通信。实现的对象IMarshal
接口表示ORPC不合适,并且对象实现者更愿意处理所有跨公寓通信 通过自定义代理。当一个对象实现IMarshal
接口时,对该对象的所有引用都将被自定义封送。当一个对象没有实现
HKEY_CLASSES_ROOT\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}
接口时,对该对象的所有引用都将是标准编组的。大多数物体选择使用标准编组。
所以问题就变成为什么标准的COM编组器有这么多问题?错误来源未注册的界面是什么?
COM的要求没有记录,但我可以告诉你,我的界面GUID 不存在于:
HRESULT CoRegisterPSClsid(
_In_ REFIID riid,
_In_ REFCLSID rclsid
);
最终将解释其原因。
我知道Windows为您提供CoRegisterPSClsid
功能,允许您在流程中注册一个接口,以便标准封送程序能够编组它:
允许下载的DLL在其运行过程中注册其自定义接口,以便编组代码能够封送这些接口。
riid
参数:
rclsid
[in]:指向要注册的接口的IID的指针。CoRegisterPSClsid(IID_IWidget, ???);
[in]:指向DLL的CLSID的指针,该DLL包含riid指定的自定义接口的代理/存根代码。我可以试试,但我使用的是什么 clsid ?
CoRegisterPSClsid(IID_IWidget, CLSID_Widget);
包含riid指定的自定义接口的代理/存根代码的DLL的CLSID是什么?我是否自己使用课程?
IPSFactoryBuffer
没有声音正确;但我不太了解COM标准封送器。并不是说CLSID必须是标准的COM编组类之一;实施HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}
(default) = "IWidget"
?
无论哪种方式,它都无法运作。我仍然收到错误"界面未注册"。
我当然可以在注册表中注册我的界面:
Interface
但这并没有解决它。通过HKEY_CLASSES_ROOT\Interface
注册表项,我注意到许多人指定 ProxyStubClsid32 条目。
在对象,代理和存根管理器上请求新接口时 必须将请求的IID解析为接口封送器的CLSID。 在Windows NT 5.0下,类存储在NT中维护这些映射 目录,它们缓存在本地注册表中的每台主机上。该 机器范围的IID到CLSID映射缓存在
HKEY_CURRENT_USER\Software\Classes\Interface
并且每个用户的映射都缓存在
HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}\TypeLib (default) = "{38D528BD-4948-4F28-8E5E-141A51090580}"
这些键中的一个或两个将包含每个已知接口的子键。如果界面安装了接口封送器,则会有一个额外的 子键(ProxyStubClsid32),指示接口的CLSID 封送。
除了什么类实现编组之外?我没有派遣人员。
如果我使用我的界面注册类型库,那么COM的标准编组程序是否可以动态引导代理类?
我在上面注册了我的界面。现在我手动包含TypeLibrary:
CoMarshalInterface
如果我在调用RegOpenKey
期间监视注册表,我会看到它尝试并找到我的界面IID:
HKCR\WOW6432Node\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}
SUCCESS
RegOpenKey
然后尝试寻找 ProxyStubClsid32 ,然后失败:
HKCR\WOW6432Node\Interface\{668790E3-83CC-47E0-907F-A44BA9A99C8D}\ProxyStubClsid32
NAME NOT FOUND
RegOpenKey
我的希望将是标准的COM封送程序试图寻找:
HKCR\WOW6432Node\Interface\{668790E3-83CC-47E0-907F-A44BA9A99C8D}\TypeLib
ProxyStubClsid32
但它没有。
根据Don Box的说法, Ole Automation 封送程序(PSOAInterface - {00020424-0000-0000-C000-000000000046})能够从类型库中构建存根/代理:
类型库Marshaler
当 RegisterTypeLib (或传统模式下的LoadTypeLib)遇到这些特殊注释的接口时,COM会为值为
{00020424-0000-0000-C0000-000000000046}
的接口添加[oleautomation]
条目。此GUID对应于注册为OLEAUT32.DLL(OLE自动化DLL)中的类 PSOAInterface 。由于它所处的DLL,这个封送器有时被称为[oleautomation]
封送器,尽管它也被称为类型库封送器或通用封送器。我将它称为类型库封送程序,因为它与IDispatch实际上没什么关系。 (事实上,在不直接或间接从IDispatch派生的接口上使用ITypeInfo
属性是很常见的。)类型库封送程序的类工厂在其 CreateProxy 和 CreateStub 例程中做了一件非常棘手的事情。而不是返回静态编译的vtable(由于OLEAUT32.DLL是作为操作系统的一部分构建时所请求的接口不存在的事实,这是不可能的),类型库编组实际上构建了一个/ Oicf样式的代理,基于接口类型库的存根。由于没有有效的方法可以找到任意界面的
HKCR\Interface\{XXX}\TypeLib
,因此必须将接口类型库的 LIBID 和版本存储在的:PSOAInterface
注册表项。
我尝试将HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}\ProxyStubClsid32
(default) = "{00020424-0000-0000-C0000-000000000046}"
设置为我的界面的ProxyStub类:
将标准COM类PSOAInterface注册为我们的代理存根clsid
HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}\TypeLib
(default) = "{38D528BD-4948-4F28-8E5E-141A51090580}"
Version = "1.0"
为我们的界面注册我们的类型库
HKEY_CURRENT_USER\Software\TypeLib\{38D528BD-4948-4F28-8E5E-141A51090580}\1.0\0\win32
(default) = "D:\Junk\ComLibraryProxyTest\Win32\Project1.dll"
类型库本身已经注册:
HKCR\Interface\[IID_IWidget]
但它仍然失败。
HKCR\Interface\[IID_IWidget]\ProxyStubClsid32
HKCR\Interface\[IID_IWidget]\TypeLib
但是:
CoMarshalInterface
因此无法代理该对象。
CoUnmarshalInterface
在输入上接受一个接口指针并写入
指向调用者提供的字节流的指针的序列化表示。然后可以将此字节流传递到另一个公寓,其中
CoUnmarshalInterface
API函数使用字节流返回接口
在语义上等同于原始对象的指针
在执行CoMarshalInterface
的公寓内合法访问
呼叫。在呼叫enum MSHCTX {
MSHCTX_INPROC = 4, // in-process/same host
MSHCTX_LOCAL = 0, // out-of-process/same host
MSHCTX_NOSHAREDMEM = 1, //16/32 bit/same host
MSHCTX_DIFFERENTMACHINE = 2, // off-host
};
时,呼叫者必须指出距离有多远
进口公寓预计将是。 COM定义了枚举
表达这个距离:
CoMarshalInterface
指定比所需距离更远的距离是合法的,但效率更高
尽可能使用正确的MSHCTX。 enum MSHLFLAGS {
MSHLFLAGS_NORMAL, // marshal once, unmarshal once
MSHLFLAGS_TABLESTRONG, // marshal once, unmarshal many
MSHLFLAGS_TABLEWEAK, // marshal once, unmarshal many
MSHLFlAGS_NOPING = 4 // suppress dist. garbage collection
也允许
调用者使用以下编组标志指定编组语义:
CoMarshalInterface
正常编组(有时称为呼叫编组)表示该 编组对象引用只能解组一次,如果是另外的 需要代理,需要额外调用{{1}}。表 编组指示封送的对象引用可能是未封送的 零次或多次,无需额外调用CoMarshalInterface。
我认为所有COM对象类型库在由MIDL编译时都可以自动创建代理/存根工厂。但就我而言:
如果COM标准封送程序无法为接口找到代理/存根工厂,则返回错误REGDB_E_IIDNOTREG。
可能是我必须要么:
答案 0 :(得分:2)
CoMarshalInterface
实际需要的是给定接口的IMarshal
实现(指针),以便API可以从中请求封送魔法,特别是请求IMarshal::GetUnmarshalClass
以便获得之后将要做反向魔法的信息:
This method is called indirectly,在对CoMarshalInterface的调用中,通过服务器进程中的任何代码负责编组指向对象上接口的指针。这个编组代码通常是由COM为几个接口之一生成的存根,它可以编组指向在完全不同的对象上实现的接口的指针。
您的小部件上没有实现IMarshal
,因此您可以从中获取它。
当你开始提问时,你想要将一个接口编组到另一个线程"并且代码评论说"创建我们的小部件"您有可能利用IMarhsal
free threaded marshaler来实施IWidget
。该问题不提供信息来判断是否可能和/或可接受的解决方案。
回到获得编组人员的挑战,你正试图通过使用"标准"来解决这个问题。 marshaler通过"注册界面":
为什么标准的COM编组器有这么多问题
[...]
我当然可以在注册表中注册我的界面
嗯,事实并非如此。
接口注册不只是一个注册表项,而且这个IID在注册表中有自己的密钥"。此类注册的目的是指出在何处查找此接口的代理/存根对。如果您没有相关接口的代理/存根DLL,则手动创建注册表项对此无效。如果你有它,你只需要用常规方法来创建注册表项。
所谓的标准编组程序,你的下一次尝试,不应该编组任何接口,特别是你的IWidget
。 OLE供应所谓的" PSOAInterface" marshaler,能够为OLE Automation兼容接口提供代理/存根对。只为他们!编组人员没有那么多问题,实际上只有一个:你的IWidget
不太可能兼容,或者你不会在第一时间遇到问题。
如果您的ProxyStubClsid32
兼容,并且有一个关联的类型库,并将其标记为[oleautomation]
,则类型库注册过程将自动创建引用PSOAInterface并提供IMarshal
的注册表项}。然后编组API会为你的小部件选择PSOAInterface,它会选择已注册的类型库,加载接口的详细信息,然后为它提供标准的代理/存根对,以及它。标准编组器只能在这些限制范围内工作,而不仅仅是指向任何接口。
也就是说,您的选择是:
IWidget
IMarshal
OLE自动化与类型库兼容,以便类型库注册过程激活"标准编组器"您的界面的PSOAInterface IWidget
,并将其注册到注册表和IWidget
接口,这样编组API会在尝试编组接口指针时选择它(我想它&如果您的IWidget
与OLE不兼容并且您有理由不改变原始实现,那么这是唯一的选择。IMarshal
在客户端,并将其IWidget
传递给CoMarshalInterThreadInterfaceInStream
API。这将强制COM使用您的编组而不更改原始服务器。当然,您可以自己进行实际的编组操作。它不太可能符合您的实际需要,它只是讨论的一个抽象的注释(主要包括尝试在没有接口本身的详细信息和修改服务器实现的可用选项的情况下做不到的事情)。
答案 1 :(得分:1)
TL; DR:实际问题:
- 标准COM " Ole Automation" 封送程序可能是否可以在运行时从类型库中构建代理类?
简答:是的。
- 我是否可以在运行时从类型库中构建代理类?
简短回答:是的,使用类型库封送程序和IPSFactoryBuffer
。或者,如果您愿意使用未记录的CreateProxyFromTypeInfo
和CreateStubFromTypeInfo
。
我想知道你为什么要这样做。
这个问题充斥着红色的鲱鱼。
我试图将接口编组到另一个线程。
(...)对
CoMarshalThreadInterfaceInStream
的调用失败:REGDB_E_IIDNOTREG (0x80040155) Interface not registered
有用的错误代码,在这种情况下。
直接转到
失败的CoMarshalInterface
(...):
REGDB_E_IIDNOTREG (0x80040155) Interface not registered
不是通过切换与您可以解决问题基本相同的API调用。
使用标准编组
我的课程没有实现
IMarhsal
界面。
您能否确认它不会实施INoMarshal
,IStdMarshalInfo
和IAgileObject
,只是为了详尽无遗?
实际上,界面未注册
这是预期的。
我知道Windows会为您提供
CoRegisterPSClsid
(...)
我可以尝试拨打电话,但我使用的是什么 clsid ?
CoRegisterPSClsid(IID_IWidget, ???);
如果你没有代理/存根,你为什么要经历这个呢?
但要回答这个问题,一个标准的编组员' CLSID通常与the first IID found in an IDL file的GUID相同。
注册界面
我当然可以在注册表中注册我的界面:
HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4} (default) = "IWidget"
但这并没有解决它。通过Interface注册表键,我注意到许多人指定 ProxyStubClsid32 条目。
您应该阅读文档,而不是依赖于其他注册表项包含的内容。
(...)我没有派遣人员。
这应该是实际问题。如果这个对象是你的,你打算如何编组呢?请参阅我的答案的底部。
COM可以根据TypeLibrary自动封送
这显然是夸夸其谈。为什么你会想到一个类型库,而不是开始?你问的其余部分证实了这一点。
现在我手动包含TypeLibrary:
HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}\TypeLib (default) = "{38D528BD-4948-4F28-8E5E-141A51090580}"
你提到过两次。
但是:
- 从不读取:HKCR \ Interface [IID_IWidget] \ TypeLib
类型库封送程序有自己的interface->类型库缓存。要清除缓存,请尝试重新创建注册表项,注销和登录,或最终重新启动。
该对象可能会实现IProvideClassInfo
,我不知道类型库封送程序是否真正关心QueryInterface
以获取运行时ITypeInfo
。
这些是编组的主要类型:
标准编组程序,通常由MIDL生成的C / C ++源代码编译成DLL(主要由关于如何编组类型的声明组成)
类型库封送程序,它可以根据自动化兼容的类型信息在运行时封送类型
使用CoCreateFreeThreadedMarshaler
聚合的自由线程编组程序,可避免在同一进程中的线程之间进行编组
自定义封送程序,它可以执行开发人员想要的任何操作,最常用于实现按值编组,或者在CoCreateFreeThreadedMarshaler
不存在时返回自由线程编组程序
MIDL生成的标准编组程序主要包括如何编组输入和输出类型的声明,因此它们的工作方式与类型库编组程序相同。根据Don Box的说法,结果非常相似。
但是,实际的声明是非常不同的。类型库封送程序处理类型信息,这些信息适用于VB6和(不包括某些内容,例如用户定义类型)脚本语言(主要是VBScript和JScript),并且旨在供IDE使用(例如VB6,VBA, Delphi,OleView)和互操作代码生成器(例如VC ++ #import
,tlbimp.exe,Delphi"导入类型库",Lisp 1 2),等