通过 COM 包装器从托管代码调用 COM 可见托管组件
我有一个第三方组件,比如说 FIPreviewHandler 来处理预览,它实现了 IPreviewHandler。 FIPreviewHandler 作为托管组件实现,并通过互操作方式使用 IPreviewHandler 接口和相关接口。 FIPreviewHandler 使用 regasm.exe 作为 COM 进行注册。
我有一个也是托管的客户端应用程序。我想在我的应用程序中创建 FIPreviewHandler 的实例作为 COM 组件。
我有一个定义 IPreviewHandler 和相关接口的互操作程序集。
当我使用 Activator.CreateInstance() 在 GetTypeByCLSID() 返回的类型上创建 FIPreviewHandler 的实例(该类型使用 FIPreviewHandler 的正确 CLSID)时,它会返回一个托管实例,因为它具有可用的实际程序集,并跳过 COM 。当我尝试将此实例 QI/cast 为任何接口(例如 IPreviewHandler)时,它返回 null,因为它是作为托管对象加载的,尽管 FIPreviewHandler 实现的 IPreviewHandler 接口与我在互操作中的接口相同,但它位于不同的命名空间/程序集中,因此为 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 对象访问非常有用,但失败了对于托管客户。
我希望我说得有道理,并且我将非常感谢任何帮助。
I have a 3rd party component, lets say FIPreviewHandler to handle preview, which implements IPreviewHandler. FIPreviewHandler is implemented as a Managed Component, and uses the IPreviewHandler interface and related interfaces through means of an interop. FIPreviewHandler is registered using regasm.exe as COM.
I have a client application which is also Managed. I want to create an instance of FIPreviewHandler as a COM component in my application.
I have an interop assembly that defines IPreviewHandler and related interfaces.
When I create an instance of FIPreviewHandler, using Activator.CreateInstance(), on a type returned by GetTypeByCLSID(), which uses the correct CLSID for FIPreviewHandler, it returns me a managed instance, as it has the actual assembly available, and skips COM. When I try to QI/cast this instance as any of the interfaces, IPreviewHandler for example, it returns null because, it is loaded as a managed object, and although the IPreviewHandler interface implemented by FIPreviewHandler is the same interface as I have in my interop, but its in a difference namespace/assembly, hence null. If it were to return me a COM instance/RCW (System.__ComObject), it would not take namespace into account, and would cast fine, and return a valid instance.
FIPreviewHandler is a 32 bit component, and on a 64bit Win7 machine, if I compile my client application as "Any CPU", Activator.CreateInstance() returns a COM instance/RCW (System.__ComObject), as it cudnt find a 64bit implementation of FIPreviewHandler, hence returns a proxy. In this scenario, my application works fine. But when I compile it for x86, it gets the 32bit implementation, and returns a managed instance of the actual managed class, and not a COM instance, hence fails.
I cannot use the interfaces defined in FIPreviewHandler's assembly, as I have to write a generic client for IPreviewHandler, and my application will work with any component implementing IPreviewHandler, which would work great for C++ based clients accessing FIPreviewHandler as a COM object, but is failing for Managed clients.
I hope I make sense and I would be really grateful for any help.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
很明显,这对 .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
So clearly this a fail on .NET's part as I'm finding there is no way to use a COM wrapper around a managed COM object.
The "solution" (and I use that term very loosely) is the use of a PIA or 'Primary Interop Assembly'. The PIA provides a single strong-named assembly imported with TlbImp.exe that is GAC registered. Basically I guess we then must rely on GAC publisher policies to force clients to the correct interface assembly.
see also
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
:如果为 FALSE,则创建一个包装器,使用反射来调用托管类型。反射很慢,但是您可以缓存您获得的
MethodInfo
对象...否则您可以生成 IL 代码,并创建一个根本没有反射的包装器。当然还有其他方法,可以做到这一点……这取决于你。
由于我热爱动态运行时 IL 生成,因此我可以为您提供执行此操作的代码...如果您感兴趣的话!
Man, if I were you, I would just make the wrapper myself, only when the type is NOT a COM type. To know if the type created is a COM type, use the
IsCOMObject
from theType
of the object:If this is FALSE create a wrapper, that uses reflection to call the managed type. Reflection is slow, but you can cache the
MethodInfo
objects that you get... otherwise you can generate IL code, and create a wrapper that will have no reflection at all.Of course there are other methods, of doing it... this is up to you.
As I am in love with dynamic run-time IL generation, I can provide you with a code that does this... if you are interested!
我的经验是,微软是故意这样做的。他们不希望您的两个托管代码程序集通过 COM 进行通信。如果您尝试在项目中添加支持 COM 作为 COM 引用的程序集,则会出现同样的错误。
如果 COM 接口是获得所需功能的唯一方法,那么非托管包装器应该可以解决问题。用 C++ 或 VB6(或任何 COM 友好的非托管语言)编写一个新的 COM 服务器,它包装了第三方托管 COM 服务器。然后将这个新的包装器 DLL 添加到您的托管代码项目中作为 COM 服务器。
My experience is that Microsoft did it this way by design. They don't want your two managed code assemblies to communicate via COM. If you try to add an assembly which supports COM as a COM reference in your project, the error says as much.
If the COM interfaces are the only way you can get at the functionality you need, a non-managed wrapper should do the trick. Write a new COM server in C++ or VB6 (or any non-managed language which is COM friendly) which wraps you third party managed COM server. Then add this new wrapper DLL to your managed code project as a COM server.
我尝试过做类似的事情但没有成功。 (在我的例子中,现有定义的 COM 接口有一个错误,这意味着它无法在 .Net 中实现,因此我自己在单独的命名空间中重新定义了 COM 接口并实现了此接口。生成的对象实现了正确的 COM接口,但是我无法获得 COM 包装器以转换回原始损坏的接口)。
据我所知,只有两种解决方案:
在 主互操作程序集(使用 TLBimp 或手动),以便所有接口都在公共命名空间中定义。 注意,此程序集通常不包含任何实现,因此不需要引用其他程序集(除非这些其他程序集也是声明依赖 COM 接口的互操作程序集)。
创建一个通过“传统”COM 而不是通过 .Net COM 互操作执行调用的包装器(例如在托管 C++ 中)。
创建一个执行调用的包装器(例如在托管 C++ 中
选项 1. 绝对让我觉得这是您的最佳选择 - 请注意,无需注册此程序集或将其放置在 GAC 中,并且只要引用通用互操作程序集,即可在完全独立的程序集中实现。
我想不出有多少合法的情况是主互操作程序集不可行的。
I've tried to do a similar thing with no success. (In my case the existing defined COM interface had a bug that meant it couldn't be implemented in .Net, so I re-defined the COM interface in a separate namespace myself and implemented this interface instead. The resulting object implemented the correct COM interfaces, however I couldn't get a hold of a COM wrapper to cast back to original broken interface).
As far as I am aware there are only 2 solutions:
Declare all COM interfaces in a Primary Interop Assembly, (either using TLBimp or by hand) so that all interfaces are defined in a common namespace. Note that this assembly normally doesn't contain any implementation, and so shouldn't need to reference other assemblies (unless those other assemblies are also interop assemblies that declare dependent COM interfaces).
Create a wrapper (for example in Managed C++) that performs the calls through "traditional" COM, rather than through the .Net COM interop.
Option 1. definitely strikes me as your best option - note that there is no need to register this assembly or place it in the GAC, and the implementation can be in an entirely separate assembly as long as the common interop assembly is referenced.
I can't think of many legitimate situations where a Primary Interop Assembly is not feasible.
听起来有两个问题:
首先,如果您知道它是托管的,为什么首先将其引用为 COM 对象呢?为什么不直接引用 DLL,而不是通过互操作,这样,您就应该像库访问接口一样访问接口。因此,您不需要调用
Activator.CreateInstance
。相反,您可以调用var foo = new FIPreviewHandler();
。然而,这并不能解决位数问题,因此这一点没有实际意义。相反...对于第二个问题,一种解决方案是将 COM 组件放入 COM+ 服务器应用程序中。 COM+ 应用程序在首次加载时确定其位数。如果 COM+ 应用程序中的所有库都是 32 位的,则加载时,它将加载到 WOW32 中,您可以从 64 位应用程序调用它。我已经对仅以 32 位形式存在的 COM 库使用了这个技巧,但我需要在 64 位服务器上使用它们。缺点是您将库加载到单独的进程中,并由于执行在应用程序的进程外而产生编组成本,但一旦实例化,它应该表现得足够好。
It sounds like there are two problems:
First, why reference it as a COM object in the first place if you know it is managed? Why not make a direct reference to the DLL instead of through interop and in that way, you should have access to the interfaces in the same way the the library has access to them. Thus, you wouldn't need to call
Activator.CreateInstance
. Instead, you would calledvar foo = new FIPreviewHandler();
. However, that does not solve the bitness problem so the point is moot. Instead...Coming to the second problem, one solution is to put the COM component into a COM+ Server Application. COM+ applications determine their bitness when they are first loaded. If all libraries in the COM+ application are 32-bit, when it is loaded, it will load into a WOW32 and you can call it from a 64-bit application. I've used this trick for COM libraries that only existed in 32-bit form but I needed the use them on a 64-bit server. The downside is that you are loading the library into a separate process and incurring the marshalling costs due to execution being out of process to your application but once instantiated, it should perform well enough.
使用 PInvoke 调用 COM 函数 CoCreateInstance(Ex) 并向其传递互操作程序集中定义的 CLSID 和 IPreviewHandler 的 IID。这样 .NET 就没有机会进行干扰。
你说你有一个互操作程序集。最好是由第三方程序集的作者分发的 PIA,但如果您必须自己构建它也没关系。 (PIA 不必在 GAC 中注册,尽管它们经常如此。)您可以通过在 IL 反汇编器中加载互操作程序集并查找类上的 System.Runtime.InteropServices.GuidAttribute 属性来获取 CLSID。
Use PInvoke to call the COM function CoCreateInstance(Ex) and pass it the CLSID defined in your interop assembly and the IID of IPreviewHandler. This way .NET gets no opportunity to interfere.
You say you have an interop assembly. It's best if this is a PIA distributed by the author of the third-party assembly, but it's fine if you have to build it yourself. (PIAs do not have to be registered in the GAC, although they often are.) You can get the CLSID by loading the interop assembly in the IL disassembler and looking up the System.Runtime.InteropServices.GuidAttribute attribute on the class.
如果我没看错的话,您需要强制它使用 COM。两个想法:
如果通过
GetTypeFromProgID()
而不是GetTypeByCLSID() 获取类型,
code>?Activator.CreateInstance()
的行为是否会改变Microsoft.VisualBasic.Interaction.CreateObject()
的行为如何?是的,我讨厌将 VB 引入其中,但我很好奇它是否返回 COM 对象而不是 .NET 类。If I'm reading this right, you need to force it to use COM. Two ideas:
Does the behavior of
Activator.CreateInstance()
change, if you get the type viaGetTypeFromProgID()
instead ofGetTypeByCLSID()
?How does
Microsoft.VisualBasic.Interaction.CreateObject()
behave? Yes, I hate to bring VB into this, but I am curious if it returns the COM object instead of the .NET class.