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

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

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

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

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

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

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

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

很明显这是.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类型时 。 要知道创建的类型是否为COM类型,请使用对象Type中的IsCOMObject

 myObject.GetType().IsCOMObject 

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

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

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

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

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

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

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

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

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

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

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

听起来有两个问题:

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

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

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

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

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

如果我正确阅读,你需要强制它使用COM。 两个想法:

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

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