通过COM包装器从托管代码调用COM可见的托管组件

16
我有一个第三方组件,比如FIPreviewHandler来处理预览,它实现了IPreviewHandler。 FIPreviewHandler被实现为托管组件,并通过interop使用IPreviewHandler接口和相关接口。 FIPreviewHandler通过regasm.exe作为COM进行注册。
我有一个客户端应用程序,也是托管的。我想在我的应用程序中创建FIPreviewHandler的COM组件实例。
我有一个Interop程序集,定义了IPreviewHandler和相关接口。
当我使用GetTypeByCLSID()返回的类型使用正确的CLSID为FIPreviewHandler创建Activator.CreateInstance()的实例时,它会将其作为托管实例返回,因为它具有实际程序集可用,并跳过COM。当我尝试将此实例QI / cast为任何接口,例如IPreviewHandler时,它返回null,因为它被加载为托管对象,尽管由FIPreviewHandler实现的IPreviewHandler接口与我在Interop中拥有的接口相同,但它在不同的命名空间/程序集中,因此为null。如果它将返回一个COM实例/RCW(System.__ComObject),它不会考虑命名空间,并且会正常强制转换并返回有效实例。
FIPreviewHandler是一个32位组件,在64位Win7机器上,如果我将客户端应用程序编译为“Any CPU”,Activator.CreateInstance()将返回一个COM实例/RCW(System.__ComObject),因为它找不到64位实现的FIPreviewHandler,因此返回代理。在这种情况下,我的应用程序工作正常。但是当我将其编译为x86时,它会获得32位实现,并返回实际托管类的托管实例而不是COM实例,因此失败。
我无法使用FIPreviewHandler程序集中定义的接口,因为我必须为IPreviewHandler编写通用客户端,我的应用程序将与实现IPreviewHandler的任何组件一起工作,这对于访问FIPreviewHandler作为COM对象的基于C ++的客户端非常有效,但对于托管客户端则失败。
我希望我的问题讲清楚了,非常感谢您的任何帮助。
7个回答

1

很明显,这是.NET的失败之处,因为我发现没有办法在托管的COM对象周围使用COM包装器。

"解决方案"(我非常宽松地使用这个术语)是使用PIA或“主要互操作程序集”。 PIA提供了一个单一的强名称程序集,由TlbImp.exe导入并注册到GAC中。基本上,我想我们必须依靠GAC发布者策略来强制客户端使用正确的接口程序集。

另请参阅

http://social.msdn.microsoft.com/Forums/en/csharpgeneral/thread/b11a0f90-fcc5-487a-b057-632f5415bfc2

http://www.codeproject.com/KB/COM/BuildCOMServersInDotNet.aspx


请记住,COM组件也必须进行注册。因此,在GAC中构建一个已注册的类型库并不那么疯狂。 - Thomas
@Thomas - 没错,GAC并不是问题,COM接口隔离的丢失才是。让我感到困惑的是,如果他们支持这一点,他们本可以更好地允许托管组件之间的隔离。然而,由于似乎没有其他选择,我们已经转向PIA使用。 - csharptest.net
你需要多少隔离?只需使用接口在两个组件之间进行通信即可。 - John Saunders

1
哥们,如果我是你,我会自己制作包装器,只有当类型不是COM类型时。要知道创建的类型是否为COM类型,请使用对象的Type中的IsCOMObject方法:
myObject.GetType().IsCOMObject

如果这是FALSE,则创建一个包装器,使用反射调用托管类型。反射很慢,但您可以缓存获取的MethodInfo对象...否则,您可以生成IL代码,并创建一个没有任何反射的包装器。
当然还有其他方法可以实现...这取决于您。
由于我热爱动态运行时IL生成,因此我可以为您提供执行此操作的代码...如果您感兴趣!

myObject.GetType().IsCOMObject 永远不会为真,要创建一个包装器,我必须从 IntPtr 偏移,将其封送到委托中,并调用它... 在我看来太危险了 ;) - csharptest.net
我觉得我把一切都搞反了。我说过:“如果对象是COM对象,那么你可以制作一个包装器”。这是错误的。很抱歉,这是我的错误。我会进行纠正。 - Miguel Angelo
即使您使用“Marshal”类从“IntPtr”进行封送,您仍然会得到托管类而不是COM包装器-我自己尝试过。 @csharptest.net - Justin

1

我的经验是微软是有意这样设计的。他们不希望你的两个托管代码程序集通过COM进行通信。如果您尝试将支持COM的程序集作为COM引用添加到项目中,错误会提示这一点。

如果COM接口是您获取所需功能的唯一方式,那么非托管包装器应该可以解决问题。使用C++或VB6(或任何非托管语言,它对COM友好)编写一个新的COM服务器,它包装了您的第三方托管COM服务器。然后将此新的包装器DLL作为COM服务器添加到您的托管代码项目中。


1

我曾尝试过类似的事情,但没有成功。(在我的情况下,现有的定义的COM接口存在一个错误,导致它无法在.Net中实现,因此我自己在一个单独的命名空间中重新定义了COM接口,并实现了这个接口。生成的对象实现了正确的COM接口,但是我无法获得一个COM包装器来强制转换回原始的损坏接口)。

据我所知,只有两种解决方案:

  1. 主互操作程序集中声明所有COM接口(可以使用TLBimp或手动声明),以便所有接口都在一个公共命名空间中定义。请注意,该程序集通常不包含任何实现,因此不应需要引用其他程序集(除非那些其他程序集也是声明依赖COM接口的Interop程序集)。

  2. 创建一个包装器(例如在托管C++中),通过“传统”的COM进行调用,而不是通过.Net COM互操作。

选项1 明显是您最佳的选择 - 请注意,无需注册此程序集或将其放置在GAC中,并且只要引用了公共Interop程序集,实现可以在完全独立的程序集中。

我想不出有多少合法情况下不能使用主互操作程序集。


0

如果我理解正确,您需要强制使用COM。 两个想法:

  1. 如果您通过GetTypeFromProgID()而不是GetTypeByCLSID()获取类型,则Activator.CreateInstance()的行为是否会改变?

  2. Microsoft.VisualBasic.Interaction.CreateObject()的行为如何? 是的,我不想把VB带进来,但我很好奇它是否返回COM对象而不是.NET类。


0

听起来有两个问题:

  1. 接口访问
  2. 组件的位数。

首先,如果您知道它是托管的,为什么要在第一次引用时将其引用为COM对象呢?为什么不直接引用DLL而不是通过互操作,并以这种方式访问接口,就像库访问它们一样。因此,您不需要调用Activator.CreateInstance。相反,您将调用var foo = new FIPreviewHandler();。但是,这并不能解决位数问题,所以这一点无关紧要。相反...

针对第二个问题,一个解决方案是将COM组件放入COM+服务器应用程序中。 COM +应用程序在首次加载时确定其位数。如果COM +应用程序中的所有库都是32位的,则在加载时,它将加载到WOW32中,并且您可以从64位应用程序中调用它。我曾经使用过这个技巧来处理仅以32位形式存在的COM库,但我需要在64位服务器上使用它们。缺点是您将库加载到单独的进程中,并且由于执行超出了应用程序,因此会产生编组成本,但一旦实例化,它应该足够好地执行。


Activator.CreateInstance实际上更受欢迎,因为它们不需要对实现进行直接引用。如果我可以强命名整个应用程序并将其全部放入GAC中,我想这会起作用;但是,我们有一些未经强命名的第三方库。 - csharptest.net
@csharptest.net - 这取决于情况。如果所涉及的库是托管的,那么直接引用即可,不需要在这种情况下使用Activator.CreateInstance。然而,如果您需要通过COM访问它,因为它不是托管的或者您正在进行上述的COM+技巧,那就不同了。 - Thomas

0
使用PInvoke调用COM函数CoCreateInstance(Ex),并向其传递在您的互操作程序集中定义的CLSID和IPreviewHandler的IID。这样,.NET就没有机会进行干扰。
您说您有一个互操作程序集。最好是由第三方程序集的作者分发的PIA,但如果您必须自己构建它,那也没关系。(PIA不必在GAC中注册,但通常会这样做。)您可以通过在IL反汇编器中加载互操作程序集并查找类上的System.Runtime.InteropServices.GuidAttribute属性来获取CLSID。

已经有一个被删除的答案建议使用 PInvoke 使用 CoCreateInstance,但不幸的是,这不起作用,因为将返回的 IntPtr 进行封送到托管对象仍然会给您托管实现类而不是 COM 包装器。 - Justin
好的。实际上,我无法重现原始问题 - 无论我做什么,最终都会得到正确的COM对象。虽然我必须承认我没有尝试过很努力。 - Ciaran Keating

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接