标准 COM 封送拆收器失败并显示 REGDB_E_IIDNOTREG

Standard COM marshaler fails with REGDB_E_IIDNOTREG

我正在尝试将接口编组到另一个线程。

Windows直接提供方便CoMarshalInterThreadInterfaceInStream helper function to take care of the boilerplate code associated with using 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

直接转到 CoMarshalInterface

COM API 函数 CoMarshalInterThreadInterfaceInStream 提供了一个围绕 CreateStreamOnHGlobalCoMarshalInterface 的简单包装器,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

使用标准编组

我的class实现IMarhsal接口。这是对的,也是正常的。

By default, when CoMarshalInterface is first called on an object, the object is asked whether it wishes to handle its own cross-apartment communications. This question comes in the form of a QueryInterface request for the IMarshal interface. Most objects do not implement the IMarshal interface and fail this QueryInterface request, indicating that they are perfectly happy to let COM handle all communications via ORPC calls. Objects that do implement the IMarshal interface are indicating that ORPC is inappropriate and that the object implementor would prefer to handle all cross-apartment communications via a custom proxy. When an object implements the IMarshalinterface all references to the object will be custom marshaled.

When an object does not implement the IMarshal interface, all references to the object will be standard marshaled. Most objects elect to use standard marshaling.

那么问题就变成了为什么标准 COM 封送拆收器有这么多问题? 接口未注册错误的来源是什么?

接口实际上没有注册

COM 的要求没有记录,但我可以告诉你我的界面 GUID 存在于:

HKEY_CLASSES_ROOT\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}

原因会在最后说明。

我知道 Windows 为您提供了 CoRegisterPSClsid 功能,允许您在进程中注册接口,以便标准编组器能够编组它:

Enables a downloaded DLL to register its custom interfaces within its running process so that the marshaling code will be able to marshal those interfaces.

HRESULT CoRegisterPSClsid(
   _In_ REFIID   riid,
   _In_ REFCLSID rclsid
);

参数:

我可以尝试调用它,但是我使用什么 clsid

CoRegisterPSClsid(IID_IWidget, ???);

包含 riid 指定的自定义接口的 proxy/stub 代码的 DLL 的 CLSID 是什么?我使用 class 本身吗?

CoRegisterPSClsid(IID_IWidget, CLSID_Widget);

听起来不对;但我不太了解 COM 标准封送拆收器。 CLSID 是否必须是标准 COM 编组之一 classes;实施 IPSFactoryBuffer?

不管怎样,都行不通。我仍然收到错误 "Interface not registered".

注册接口

我当然可以在注册表中注册我的接口:

HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}
      (default) = "IWidget"

但这并不能解决问题。通过 Interface 注册表项进行 Spelunking,我注意到许多指定了 ProxyStubClsid32 条目。

When a new interface is requested on an object, the proxy and stub managers must resolve the requested IID onto the CLSID of the interface marshaler. Under Windows NT 5.0, the class store maintains these mappings in the NT directory, and they are cached at each host machine in the local registry. The machine-wide IID-to-CLSID mappings are cached at

HKEY_CLASSES_ROOT\Interface

and the per-user mappings are cached at

HKEY_CURRENT_USER\Software\Classes\Interface

One or both of these keys will contain a subkey for each known interface. If the interface has an interface marshaler installed, there will be an additional subkey (ProxyStubClsid32) that indicates the CLSID of the interface marshaler.

除了什么 class 实现编组?我没有编组器。

COM 能否基于 TypeLibrary 自动编组

如果我用我的接口注册一个类型库,COM 的标准封送拆收器是否可以bootstrap 代理 class 即时?

我在上面注册了我的接口。现在我手动包含 TypeLibrary:

HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}\TypeLib
      (default) = "{38D528BD-4948-4F28-8E5E-141A51090580}"

如果我在调用 CoMarshalInterface 期间监视注册表,我会看到它尝试并确实找到了我的接口 IID:

然后它尝试寻找 ProxyStubClsid32,但失败了:

我的希望将是标准 COM 封送拆收器尝试寻找:

但事实并非如此。

OLE 自动化编组器

根据 Don Box 的说法,Ole Automation 编组器 (PSOAInterface - {00020424-0000-0000-C000-000000000046}) 能够构建 stub/proxy 输出类型库的:

The Type Library Marshaler

When these specially annotated interfaces are encountered by RegisterTypeLib (or LoadTypeLib in legacy mode), COM adds ProxyStubClsid32 entries for the interface with the value {00020424-0000-0000-C0000-000000000046}. This GUID corresponds to the class PSOAInterface that is registered as living in OLEAUT32.DLL, the OLE automation DLL. Because of the DLL that it lives in, this marshaler is sometimes called the [oleautomation] marshaler, although it is also called the type library marshaler or the universal marshaler. I'll refer to it as the type library marshaler, since it really has very little to do with IDispatch. (In fact, it is common to use the [oleautomation] attribute on interfaces that don't derive from IDispatch directly or indirectly.)

The class factory for the type library marshaler does something very tricky in its CreateProxy and CreateStub routines. Rather than return a statically compiled vtable (which is impossible given the fact that the requested interface didn't exist when OLEAUT32.DLL was built as part of the OS), the type library marshaler actually builds an /Oicf-style proxy and stub based on the type library for the interface. Because there is no efficient way to find the ITypeInfo for an arbitrary interface, the LIBID and version of the interface's type library must be stored under the:

HKCR\Interface\{XXX}\TypeLib

registry key.

我尝试将 PSOAInterface 设置为我的接口的 ProxyStub class:

但是还是失败了

但是它:

所以代理对象失败。

问题

背景

CoMarshalInterface 在输入上获取接口指针并写入 指向调用者提供的字节流的指针的序列化表示。这个字节流然后可以被传递到另一个公寓,在那里 CoUnmarshalInterface API函数使用字节流到return接口 语义上等同于原始对象的指针,但可以 在执行 CoUnmarshalInterface 的公寓中合法访问 称呼。拨打CoMarshalInterface时,来电者须注明距离多远 预计进口公寓。 COM 定义了一个枚举 表达这个距离:

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
};

指定比要求更大的距离是合法的,但效率更高 尽可能使用正确的 MSHCTX。 CoMarshalInterface 也允许 调用者使用以下封送处理标志指定封送处理语义:

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。 Table 封送处理指示封送处理的对象引用可能被取消封送处理 零次或多次,无需额外调用 CoMarshalInterface。

我认为所有的 COM 对象类型库,当被 MIDL 编译时,都可以自动创建一个 proxy/stub 工厂。但就我而言:

if the COM standard marshaler can't find a proxy/stub factory for an interface, it returns the error REGDB_E_IIDNOTREG.

可能是这样的情况,我必须:

红利阅读

CoMarshalInterface 实际需要的是给定接口的 IMarshal 实现(指针),以便 API 可以从中请求编组魔法,特别是请求 IMarshal::GetUnmarshalClass为了获得信息之后谁要施展逆向魔法:

This method is called indirectly, in a call to CoMarshalInterface, by whatever code in the server process is responsible for marshaling a pointer to an interface on an object. This marshaling code is usually a stub generated by COM for one of several interfaces that can marshal a pointer to an interface implemented on an entirely different object.

您没有在您的小部件上实现 IMarshal,因此您将从某个地方获取它。

当你开始你的问题时提到你 "want to marshal an interface to another thread" 并且代码注释说 "Create our widget" 有机会你可以利用 IMarhsal 实现 free threaded marshaler。该问题未提供信息来判断是否可能 and/or 可接受的解决方案。

回到获取封送拆收器的挑战,您正在尝试通过 "registering the interface":

使用 "standard" 封送拆收器来解决这个问题

why is the standard COM marshaler having so much problems

[...]

I of course can register my interface in the registry

嗯,实际情况并非如此。

接口注册不仅仅是"okay, this IID has its own key in the registry"的注册表项。这种注册的目的是指出在哪里寻找这个接口的 proxy/stub 对。如果您没有用于相关接口的 proxy/stub DLL,您手动创建注册表项在这里无济于事。如果你有它,你只需 regsvr32 它创建注册表项的常用方法。

所谓的标准封送拆收器,您的下一次尝试,不应该封送任何接口,尤其是您的 IWidget。 OLE 提供所谓的 "PSOAInterface" 编组器,它能够为 OLE 自动化兼容接口提供 proxy/stub 对。只为他们!封送拆收器没有那么多问题,它实际上只有一个:您的 IWidget 不太可能兼容,否则您一开始就不会有问题。

如果您的 IWidget 是兼容的,有一个关联的类型库,它被标记为 [oleautomation],类型库注册过程会自动创建引用 PSOAInterface 的注册表项并提供 ProxyStubClsid32。然后编组 API 会为您的小部件选择 PSOAInterface,它会选择已注册的类型库,加载接口的详细信息,然后为其提供标准 proxy/stub 对,仅此而已。标准封送拆收器仅在这些限制范围内工作,而不仅仅适用于指向的任何接口。

也就是说,您的选择是:

  • 在小部件服务器上实施 IMarshal
  • 使 IWidget OLE 自动化与类型库兼容,以便类型库注册过程为您的接口激活 "standard marshaler" PSOAInterface
  • 如果可能和适用,构建由 MIDL 编译器自动生成的接口的 proxy/stub 实现(您可以检查从 Visual Studio 模板创建的标准 ATL DLL 项目,了解如何完成 -它创建带有 "PS" 后缀的附加项目,用于补充 proxy/stub DLL)
  • 在一个独立的 DLL 中单独实现 IMarshal,并在注册表和您的 IWidget 接口中注册它,以便编组 API 在尝试编组接口指针(如果您的 IWidget 与 OLE 不兼容并且您有理由不更改原始实现,我想这是这里唯一的选择)
  • 如果限制可以接受,请在小部件服务器中使用免费的线程封送拆收器

哦等等,等等 - 还有一个奇怪的。如果您不想或负担不起,或者您不愿意更改 COM 服务器(即小部件)但您可以随意修改客户端代码,则可以创建一个实现两个接口的瘦包装器 IWidget(所有方法将调用转发到真实服务器)和客户端的 IMarshal,并将其 IWidget 传递给 CoMarshalInterThreadInterfaceInStream API。这将强制 COM 在不更改原始服务器的情况下使用您的封送处理。当然,您需要自己在之后进行实际的编组。它不太可能符合您的实际需要,它只是对讨论的抽象说明(主要包括在没有接口本身细节和修改服务器实现的可用选项的情况下尝试做不可能的事情)。

TL;DR:实际问题:

  • Is it possible for the standard COM "Ole Automation" marshaler to build a proxy class out of a Type Library at runtime?

简短回答:是。

  • Is it possible for me to build a proxy class out of a Type Library at runtime?

简短回答:是的,使用类型库封送拆收器和 IPSFactoryBuffer。或者,如果您愿意使用未记录的 CreateProxyFromTypeInfoCreateStubFromTypeInfo.

我想知道你为什么想要这个。


这个问题充满了歧义。

I'm trying to marshal an interface to another thread.

(...) the call to CoMarshalThreadInterfaceInStream fails with:

REGDB_E_IIDNOTREG (0x80040155)
Interface not registered

一个有用的错误代码,在这种情况下。

Go directly to CoMarshalInterface

(...) that fails with:

REGDB_E_IIDNOTREG (0x80040155)
Interface not registered

不是通过切换 API 可以解决您的问题的本质上相同的调用。

Use standard marshaling

My class does not implement IMarhsal interface.

您能否确认它也没有实现 INoMarshal, IStdMarshalInfo and IAgileObject,只是为了详尽无遗?

The interface is, in fact, not registered

这是预料之中的。

I know that Windows provides you the CoRegisterPSClsid

(...)

Which i can try calling, but what clsid do i use?

CoRegisterPSClsid(IID_IWidget, ???);

如果你没有proxy/stub,你为什么要经历这个?

但要回答这个问题,标准封送拆收器的 CLSID 通常与 the first IID found in an IDL file 的 GUID 相同。

Register the interface

I of course can register my interface in the registry:

HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}
      (default) = "IWidget"

But that doesn't fix it. Spelunking through the Interface registry keys, i notice that many specify a ProxyStubClsid32 entry.

您应该阅读文档,而不是依赖其他注册表项包含的内容。

(...) I don't have a marshaler.

这应该是实际问题。如果这个对象是你的,你打算如何编组它?请看我回答的底部。

Can COM automatically marshal based on a TypeLibrary

这明显是花言巧语。您为什么现在才想到类型库,而不是一开始就想到?你的问题的其余部分证实了。

Now i manually include the TypeLibrary:

HKEY_CURRENT_USER\Software\Classes\Interface\{EBBC7C04-315E-11D2-B62F-006097DF5BD4}\TypeLib
      (default) = "{38D528BD-4948-4F28-8E5E-141A51090580}"

你提了两次。

But it:

  • never reads: HKCR\Interface[IID_IWidget]\TypeLib

类型库编组器有自己的接口->类型库缓存。要清除缓存,请尝试重新创建注册表项、注销并重新登录,或者最终重新启动。

该对象可能会实现 IProvideClassInfo,我不知道类型库封送拆收器是否真的关心 QueryInterface 为此获取运行时 ITypeInfo 来使用。


这些是编组器的主要类型:

  • 标准封送拆收器,通常从 C/C++ MIDL 生成的源代码编译成 DLL(主要由关于如何封送类型的声明组成)

  • 类型库封送拆收器,它可以在运行时根据自动化兼容的类型信息封送类型

  • CoCreateFreeThreadedMarshaler聚合的自由线程编组器,避免了同一进程内线程之间的编组

  • 自定义编组器,开发人员可以做任何想做的事情,最常见的是实现按值编组,或者在 CoCreateFreeThreadedMarshaler 不存在时实现自由线程编组器

MIDL 生成的标准编组器主要包含有关如何编组类型输入和输出的声明,因此它们的工作方式与类型库编组器是一脉相承的。根据 Don Box 的说法,结果非常相似。

然而,实际的声明却大不相同。类型库封送拆收器处理旨在在 VB6 和(不包括某些东西,例如用户定义类型)脚本语言(主要是 VBScript 和 JScript)中工作的类型信息,并且旨在由 IDE(例如 VB6,VBA、Delphi、OleView) 和互操作代码生成器(例如 VC++ #import、tlbimp.exe、Delphi 的 "Import Type Library"、 Lisp 1 2), 等等