为什么不能通过COM在.NET中使用.NET COM库?

20
现在,我知道如果库是.NET的,通过COM访问它有点毫无意义,但是,我有些困惑,因为如果我要求某人编写一个库并通过COM公开它,那个人应该可以使用任何语言自由地这样做。
对我来说,这个COM库是用哪种语言编写的并不重要,那么为什么会有影响呢?
供参考,这是当您在.NET库上生成.tlb文件时使用tlbimp时会发生的情况:
C:\dev>tlbimp test.tlb
Microsoft (R) .NET Framework Type Library to Assembly Converter 4.0.30319.1
Copyright (C) Microsoft Corporation.  All rights reserved.

TlbImp : error TI1029 : Type library 'test' was exported from
a CLR assembly and cannot be re-imported as a CLR assembly.

此外,我的测试COM库使用IUnknown,仅支持早期绑定的COM互操作。

pst:tlbimp.exe拒绝让我导入类型。 - Arafangion
那么,换句话说,这应该被理解为“微软强加的愚蠢限制只是为了让人不方便”?他们肯定有原因吧? - Arafangion
1
如何尝试使用 tlbexp(在程序集上)-> tlb -> tlbimp /tlb(到新的程序集)呢?否则,可以手动转换(从 tlb 或原始程序集类型),但可能有些繁琐。 - user166390
1
“tlbexp -> tlb” 部分没问题,但是 “/tlb” 标志没有文档说明,除非你指的是 “/tlbreference”,它只指定如何解析引用。然而,如果您提供一个 .tlb 文件(如所示),则会生成一个新的程序集。(据我所知,这是正常的操作方式)。 - Arafangion
1
虽然我已经看到有人说过这是“无意义的”,但我个人认为,想要通过.NET中的COM访问公开的COM .NET类并不是毫无意义。如果您只想一次性公开COM的功能(正如COM所预期的那样)-但您既有VB6客户端又有.NET客户端-那么您几乎被迫为.NET客户端注册到GAC……这在一般情况下并不是一个推荐的做法。请参见我的问题,在这里 - transistor1
显示剩余3条评论
2个回答

21

tlbimp只是问题的开始。

每个COM-visible .NET对象都实现了IManagedObject接口。每当您尝试通过COM接口调用对象时,.NET都会对IManagedObject进行QueryInterface操作,如果成功,则对象将被取消包装并直接访问。以这种方式消除interop调用被认为是性能优化。

在基于COM的互操作场景中,这是一个严重的问题,其中多个组件可能是用.NET语言实现的。如果每个.NET组件都实现其自己的tlbimp生成的COM接口版本,则它们将无法使用该接口相互通信。即使每个tlbimp'ed的接口副本具有相同的COM GUID,.NET仍然认为它们是分开的接口。解决此问题的“正确”方法是在主互操作程序集中定义COM接口,并使每个.NET实现的组件使用该接口的同一副本,但如果组件开发者之间没有协调,那么这很不可能发生。

这个问题曾经在http://blogs.msdn.com/b/jmstall/archive/2009/07/09/icustomqueryinterface-and-clr-v4.aspx上被微软的一位开发人员强调,并提供了.NET 4的解决方案,但它是基于一个预发布版本,不适用于最终版本。我不知道微软在其他地方是否承认了这个问题。

一种解决方法是在.NET-to-.NET的场景中放弃接口,改为使用反射。这在许多情况下可能有效,但当然性能不太好。可能最好的解决方法是使用非托管代码来聚合您的每个托管组件,并拒绝对IManagedObject进行QueryInterface操作的尝试。这类似于上述博客文章中描述的内容,但不依赖于不再按照该博客中所述方式工作的.NET功能。

.NET的互操作行为,我认为相当糟糕,明显违反COM规则(相同IID==相同接口,不应该关心对象是用哪种语言实现的)。但.NET从一开始就表现出这种方式,并且对于受到这种行为伤害的开发人员的任何反馈都没有改变.NET团队中任何人的想法。


啊,太糟糕了。你能提供参考资料吗? - Arafangion
全面糟糕。你能把那些参考资料放在上面的答案里吗?这样你就能得到赏金了。 - Arafangion
把所有东西合并在一起了。抱歉,我还没有经常使用SO,我还不习惯编辑而不是发布。 - j__m
太棒了。:) 谢谢你提供的信息!(我需要再等待5个小时才能颁发悬赏) - Arafangion

7
Tlbimp能够看出类型库是从.NET程序集生成的,但它却在你所尝试做的事情上犯了难。如果你想使用一个.NET程序集,那么只需要添加一个引用就可以了,这很好理解,为什么会发生这种情况呢?
你可以通过使用C#中的dynamic关键字来进行后期绑定的COM操作来欺骗计算机。这种方法最容易实现,但CLR仍然能够看到它与托管程序集的互操作性,并不会真正创建COM可调用包装器。通常你会使用这种方法来编写测试程序以测试COM服务器。其含义是你的测试并不能真正测试COM服务器在实践中的使用方式。一些基本的操作比如将变量转换为对象和反之操作可能根本没有被测试。这可能会给你在生产中使用程序集时带来非常不愉快的惊喜。

但是晚期绑定的COM只能与IDispatch一起使用,IUnknown需要早期绑定的COM;事实上,正是为了系统测试,我希望使用COM接口。 - Arafangion
你可以免费获得IDispatch,所以避免使用它没有太大意义。但是,我的测试建议在这里同样适用。 - Hans Passant
客户端应用程序将使用IUnknown,因此避免使用IDispatch意味着我强制我的测试使用IUnknown接口。 - Arafangion
对于.NET如何处理托管COM库的好信息表示赞同,但我不同意通过COM访问它没有意义的观点。个人经历是,当我尝试使用后期绑定的COM来解决这个问题时,遇到了一些“不愉快的行为”。在我的开发机上,后期绑定调用按预期工作;但是当我将其部署到另一台机器上时,我在Marshal.FinalReleaseComObject()上得到了一个异常。似乎.NET在我的开发机上将我的COM对象视为System.__ComObject,在我的部署机器上则视为另一种类型的对象。我没有深入研究,而是放弃了它。 - transistor1

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