标准COM封送程序因REGDB_E_IIDNOTREG

时间:2017-07-19 16:18:23

标签: winapi com typelib

我试图将接口编组到另一个线程。

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

直接转到CoMarshalInterface

COM API函数 CoMarshalInterThreadInterfaceInStream 提供CreateStreamOnHGlobalCoMarshalInterfaceas 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自动封送

如果我使用我的界面注册类型库,那么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

但它没有。

OLE自动化编组程序

根据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

因此无法代理该对象。

问题

  • 标准COM " Ole Automation" 封送程序可能是否可以在运行时从类型库中构建代理类?
  • 是否可以在运行时从类型库中构建代理类?

背景

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。

可能是我必须要么:

  • 使用CreateProxyFromTypeInfo和CreateStubFromTypeInfo创建自己的代理
  • 如果有与接口GUID关联的typeinfo块,则标准COM封送程序会自动创建代理/存根。

奖金阅读

2 个答案:

答案 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
  • 构建,如果可能且适用,MIDL编译器自动生成您的接口的代理/存根实现(您可以检查标准ATL DLL项目,从Visual Studio模板创建如何完成 - 它使用&#创建其他项目34; PS"后缀,用于补充代理/存根DLL)
  • 在一个独立的DLL中单独实现IWidget,并将其注册到注册表和IWidget接口,这样编组API会在尝试编组接口指针时选择它(我想它&如果您的IWidget与OLE不兼容并且您有理由不改变原始实现,那么这是唯一的选择。
  • 如果可以接受限制,则在小部件服务器中使用免费的线程编组程序
等等,等等 - 还有另一个奇怪的。如果您不想要或者买不起,或者您不愿意更改COM服务器(小部件,但是)您可以根据需要修改客户端代码,则可以创建一个实现两个接口的精简包装器{{ 1}}(所有方法将调用转发给真实服务器)和IMarshal在客户端,并将其IWidget传递给CoMarshalInterThreadInterfaceInStream API。这将强制COM使用您的编组而不更改原始服务器。当然,您可以自己进行实际的编组操作。它不太可能符合您的实际需要,它只是讨论的一个抽象的注释(主要包括尝试在没有接口本身的详细信息和修改服务器实现的可用选项的情况下做不到的事情)。

答案 1 :(得分:1)

TL; DR:实际问题:

  
      
  • 标准COM " Ole Automation" 封送程序可能是否可以在运行时从类型库中构建代理类?
  •   

简答:是的。

  
      
  • 是否可以在运行时从类型库中构建代理类?
  •   

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

我想知道你为什么要这样做。

这个问题充斥着红色的鲱鱼。

  

我试图将接口编组到另一个线程。

     

(...)对CoMarshalThreadInterfaceInStream的调用失败:

REGDB_E_IIDNOTREG (0x80040155)
Interface not registered

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

  

直接转到CoMarshalInterface

     失败的

(...):

REGDB_E_IIDNOTREG (0x80040155)
Interface not registered

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

  

使用标准编组

     

我的课程没有实现IMarhsal界面。

您能否确认它不会实施INoMarshalIStdMarshalInfoIAgileObject,只是为了详尽无遗?

  

实际上,界面未注册

这是预期的。

  

我知道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不存在时返回自由线程编组程序

    < / LI>

MIDL生成的标准编组程序主要包括如何编组输入和输出类型的声明,因此它们的工作方式与类型库编组程序相同。根据Don Box的说法,结果非常相似。

但是,实际的声明是非常不同的。类型库封送程序处理类型信息,这些信息适用于VB6和(不包括某些内容,例如用户定义类型)脚本语言(主要是VBScript和JScript),并且旨在供IDE使用(例如VB6,VBA, Delphi,OleView)和互操作代码生成器(例如VC ++ #import,tlbimp.exe,Delphi&#34;导入类型库&#34;,Lisp 1 2),等