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

时间:2010-06-25 16:09:17

标签: c# com interop proxy marshalling

我有第三方组件,让我们说FIPreviewHandler来处理预览,它实现了IPreviewHandler。 FIPreviewHandler实现为托管组件,并通过互操作使用IPreviewHandler接口和相关接口。 FIPreviewHandler使用regasm.exe作为COM注册。

我有一个也是Managed的客户端应用程序。我想在我的应用程序中创建一个FIPreviewHandler实例作为COM组件。

我有一个interop程序集,它定义了IPreviewHandler和相关接口。

当我使用Activator.CreateInstance()创建一个FIPreviewHandler实例时,GetTypeByCLSID()返回的一个类型,它使用FIPreviewHandler的正确CLSID,它返回一个托管实例,因为它有实际的程序集可用,并跳过COM。当我尝试QI /将此实例转换为任何接口,例如IPreviewHandler时,它返回null,因为它作为托管对象加载,尽管FIPreviewHandler实现的IPreviewHandler接口与我在interop中的接口相同,但它在一个不同的命名空间/程序集中,因此为null。如果要返回一个COM实例/ RCW(System .__ ComObject),它将不会占用命名空间,并且可以正常转换,并返回一个有效的实例。

FIPreviewHandler是一个32位组件,在64位Win7机器上,如果我将我的客户端应用程序编译为“任何CPU”,Activator.CreateInstance()将返回一个COM实例/ RCW(System .__ ComObject),因为它会查找FIPreviewHandler的64位实现,因此返回一个代理。在这种情况下,我的应用程序工作正常。但是当我为x86编译它时,它获得32位实现,并返回实际托管类的托管实例,而不是COM实例,因此失败。

我无法使用FIPreviewHandler程序集中定义的接口,因为我必须为IPreviewHandler编写通用客户端,并且我的应用程序将与任何实现IPreviewHandler的组件一起使用,这对于访问FIPreviewHandler作为COM对象的基于C ++的客户端非常有用,但是托管客户失败了。

我希望我有意义,我会非常感激任何帮助。

7 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

伙计,如果我是你,我会自己创建包装器,只有当类型不是COM类型时才会这样做。要知道创建的类型是否为COM类型,请使用对象的IsCOMObject中的Type

myObject.GetType().IsCOMObject

如果这是 FALSE 创建一个包装器,它使用反射来调用托管类型。反射很慢,但你可以缓存你得到的MethodInfo个对象......否则你可以生成IL代码,并创建一个根本没有反射的包装器。

当然还有其他方法,这样做......这取决于你。

由于我喜欢动态运行时IL生成,我可以为您提供执行此操作的代码...如果您有兴趣!

答案 2 :(得分:1)

我的经验是微软通过设计这样做。他们不希望您的两个托管代码程序集通过COM进行通信。如果您尝试在项目中添加支持COM作为COM引用的程序集,则错误说明了。

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

答案 3 :(得分:1)

我试图做类似的事但没有成功。 (在我的情况下,现有的定义的COM接口有一个错误,意味着它无法在.Net中实现,所以我自己在一个单独的命名空间中重新定义了COM接口,并实现了这个接口。生成的对象实现了正确的COM接口,但我无法控制COM包装器以回退到原来的破坏接口。)

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

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

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

  3. 选项1.肯定是我最好的选择 - 请注意,无需注册此程序集或将其放在GAC中,只要引用了公共互操作程序集,实现就可以在完全独立的程序集中进行

    我无法想到主要互操作程序集不可行的许多合法情况。

答案 4 :(得分:0)

如果我正确读取此权限,则需要强制它使用COM。两个想法:

  1. 如果您通过Activator.CreateInstance()而非GetTypeFromProgID()获取类型,GetTypeByCLSID()的行为是否会发生变化?

  2. Microsoft.VisualBasic.Interaction.CreateObject()的行为如何?是的,我讨厌将VB引入其中,但我很好奇它是否返回COM对象而不是.NET类。

答案 5 :(得分:0)

听起来有两个问题:

  1. 访问界面
  2. 组件的比特。
  3. 首先,如果您知道它被管理,为什么首先将它作为COM对象引用?为什么不直接引用DLL而不是通过interop,以这种方式,您应该以与库访问它们相同的方式访问接口。因此,您无需致电Activator.CreateInstance。相反,你会打电话给var foo = new FIPreviewHandler();。但是,这并不能解决比特问题,所以重点是没有实际意义。代替...

    出现第二个问题,一个解决方案是将COM组件放入COM + Server应用程序。 COM +应用程序在首次加载时确定它们的位数。如果COM +应用程序中的所有库都是32位,则在加载时,它将加载到WOW32中,您可以从64位应用程序调用它。我已经将这个技巧用于仅以32位形式存在的COM库,但我需要在64位服务器上使用它们。缺点是您正在将库加载到一个单独的进程中,并且由于执行过程导致您的应用程序失去编组成本,但一旦实例化,它应该表现得足够好。

答案 6 :(得分:0)

使用PInvoke调用COM函数CoCreateInstance(Ex)并将其传递给interop程序集中定义的CLSID和IPreviewHandler的IID。这样.NET就没有机会干涉。

你说你有一个互操作程序集。如果这是由第三方程序集的作者分发的PIA,那是最好的,但是如果你必须自己构建它就没关系。 (PIA不必在GAC中注册,尽管它们通常是。)您可以通过在IL反汇编程序中加载互操作程序集并在类上查找System.Runtime.InteropServices.GuidAttribute属性来获取CLSID。 p>