.NET - COM 互操作性






4.56/5 (42投票s)
本文档提供了关于 .NET 和 COM 互操作的技术概述。
摘要
本文档提供了 .NET 和 COM 互操作的技术概述。它描述了 .NET 组件如何与现有的 COM 组件通信,而无需将这些 COM 组件迁移到 .NET 组件,从而有助于降低迁移成本和企业系统。本文档还概述了封送处理。本文档的目标读者是希望与 COM 和 .NET 应用程序交互的开发团队。(本文档假定读者具备 COM 和 .NET 的基本知识)
引言
从 1998 年 Microsoft 工程师开始研究 COM 的想法以来,COM 经历了相当大的演变。一旦 .NET 发布,一切都围绕着 CLR 展开。这些企业系统在 COM 开发方面进行了大量投资,它们可能不愿意投入更多资金将组件构建到 .NET 中。这也会对生产力产生严重影响。
幸运的是,从 COM 切换到 .NET 不会导致生产力的如此大的损失。提供 .NET 和 COM 组件之间桥梁的概念就是 .NET-COM 互操作。Microsoft .NET Framework 提供了系统、工具和策略,可以实现与旧技术的强大集成,并允许将遗留代码与新的 .NET 组件集成。它在 .NET 和 COM 之间以及反之亦然提供了桥梁。
有两个关键概念可以更容易地从 COM 开发迁移到 .NET 开发,而不会丢失任何代码库或生产力。
- 从 .NET 与 COM 组件交互
- 从 COM 与 .NET 组件交互
在深入探讨之前,本文将描述 COM 和 .NET 组件的基本通信基础。
对象与客户端之间的通信
COM 是一个二进制可重用对象,它将其功能暴露给其他组件。当客户端对象请求服务器对象的实例时,服务器会实例化这些对象并将其引用交给客户端。因此,COM 组件可以充当调用方和被调用方之间的二进制契约。此二进制契约在称为类型库的文档中定义。类型库向潜在客户端描述特定服务器可用的服务。每个 COM 组件都会公开一组接口,COM 组件之间的通信将通过这些接口进行。
下图显示了客户端和 COM 对象之间的通信。
图 1. 客户端和 COM 对象之间的通信
在上图中,IUnknown
和 IDispatch
是接口,QueryInterface
, AddRef
, Release
等是由这些接口公开的方法。
.NET 对象之间的通信通过对象进行,没有专门的通信接口。因此,在 .NET 组件中,没有类型库,取而代之的是程序集。程序集是组合在一起形成逻辑功能单元的类型和资源的集合。所有与程序集相关的信息都包含在程序集元数据中。与 COM 组件之间的通信不同,.NET 组件之间的通信是基于对象的。
从 .NET 客户端调用 COM 组件
通常 COM 组件会公开接口与其他对象进行通信。.NET 客户端无法直接与 COM 组件通信,因为 .NET 应用程序可能无法读取 COM 组件公开的接口。因此,要与 COM 组件通信,需要以 .NET 客户端应用程序能够理解的方式包装 COM 组件。此包装器称为运行时可调用包装器 (RCW)。
.NET SDK 提供了运行时可调用包装器 (RCW),它包装 COM 组件并将其公开给 .NET 客户端应用程序。
图 2. 从 .NET 客户端调用 COM 组件
要与 COM 组件通信,必须有运行时可调用包装器 (RCW)。RCW 可以通过 VS.NET 或使用 TlbImp.exe 实用程序生成。这两种方法都会读取类型库并使用 System.Runtime.InteropServices.TypeLibConverter
类生成 RCW。此类读取类型库并将这些描述转换为包装器 (RCW)。生成 RCW 后,.NET 客户端应导入其命名空间。现在,客户端应用程序可以像调用本地调用一样调用 RCW 对象。
当客户端调用函数时,调用会传输到 RCW。RCW 内部调用本地 COM 函数 coCreateInstance,从而创建其包装的 COM 对象。RCW 将每个调用转换为 COM 调用约定。一旦对象成功创建,.NET 客户端应用程序就可以像调用本地对象一样访问 COM 对象。
从 COM 客户端调用 .NET 组件
当 COM 客户端请求服务器时,它首先在注册表条目中进行搜索,然后开始通信。从 COM 组件调用 .NET 组件并非易事。.NET 对象通过对象进行通信。但是 COM 客户端可能无法识别基于对象的通信。因此,要从 COM 组件与 .NET 组件通信,需要以 COM 客户端能够识别的方式包装 .NET 组件。此包装器称为 COM 可调用包装器 (CCW)。COM 可调用包装器 (CCW) 将用于包装 .NET 组件并与 COM 客户端交互。
CCW 由 .NET 实用程序 RegAsm.exe 创建。它读取 .NET 组件的元数据并生成 CCW。此工具将为 .NET 组件创建注册表条目。
图 3. 从 COM 客户端调用 .NET 组件
通常 COM 客户端通过其本地方法 coCreateInstance
实例化对象。在与 .NET 对象交互时,COM 客户端通过 CCW 使用 coCreateInstance
创建 .NET 对象。
内部,当调用 coCreateInstance
时,调用将重定向到注册表条目,注册表会将调用重定向到已注册的服务器 mscoree.dll。此 mscoree.dll 将检查请求的 CLSID 并读取注册表以查找包含该类的 .NET 类和程序集,并为该 .NET 类生成 CCW。
当客户端调用 .NET 对象时,调用首先会到达 CCW。CCW 将所有本地 COM 类型转换为其 .NET 等效类型,并将结果从 .NET 转换回 COM。
.NET-COM 互操作的编程模型比较
下表比较了 .NET 和 COM 基于组件的编程模型。
.NET |
COM |
基于对象的通信 |
基于接口的通信 |
使用垃圾回收器管理内存 |
使用引用计数管理内存 |
类型化标准对象 |
二进制标准对象 |
对象由普通 new 运算符创建 |
对象由 |
返回异常 |
返回 |
对象信息驻留在程序集文件中 |
对象信息驻留在类型库中 |
在应用程序开始通信之前,存在一些相关的技术限制。当一个对象传输到位于不同机器/进程(托管/非托管)空间的接收方时,对象可能需要根据本地类型进行转换,以便接收方可以使用。即对象将被转换为接收方可读的格式。当跨上下文发送对象时,将对象在类型之间进行转换的过程称为封送处理。本文档的下一节将概述 .NET 中的封送处理。
.NET 封送处理
这样,.NET 运行时会自动生成代码来翻译托管代码和非托管代码之间的调用。在这些代码之间传输调用时,.NET 还会处理数据类型转换。这种自动将服务器数据类型绑定到客户端数据类型的技术称为封送处理。封送处理发生在托管堆和非托管堆之间。例如,图 4 显示了从 .NET 客户端到 COM 组件的调用。此示例调用传递一个 .NET 字符串。RCW 将此 .NET 数据类型转换为 COM 兼容数据类型。在这种情况下,COM 兼容数据类型是 BSTR
。因此,RCW 将 .NET 字符串转换为 COM 兼容的 BSTR
。此 BSTR
将传递给对象并进行必要的调用。结果将返回给 RCW。RCW 将此 COM 兼容结果转换为 .NET 本地数据类型。
图 4. 封送处理的示例图
从逻辑上讲,封送处理可以分为 2 种类型。
- 互操作封送处理
- COM 封送处理
如果调用发生在同一单元内的托管代码和非托管代码之间,互操作封送器将发挥作用。它在托管代码和非托管代码之间封送数据。
在某些情况下,COM 组件可能在不同的单元线程中运行。在那些情况下,即在不同单元或进程中调用托管代码和非托管代码时,互操作封送器和 COM 封送器都将参与其中。
互操作封送器
当服务器对象在客户端的同一单元中创建时,所有数据封送都由互操作封送处理。.
图 5. 同一单元封送处理的示例图
COM 封送器
当托管代码和非托管代码之间的调用位于不同的单元时,会涉及 COM 封送处理。例如,当 .NET 客户端(具有默认单元设置)与 COM 组件(无论由 VB6.0 开发)通信时,通信通过代理和存根进行,因为两个对象都将在不同的单元线程中运行。(.NET 对象的默认单元设置为 STA,而 VB6.0 开发的组件是 STA)。在这两个不同的单元之间将发生 COM 封送处理,而在单元内部将发生互操作封送处理。图 6 显示了这种封送处理。
这种不同单元的通信会影响性能。可以通过更改 STAThreadAttribute / MTAThreadAttribute / Thread.ApartmentState 属性来更改托管客户端的单元设置。通过将托管代码的线程设置为 STA,两个代码都可以运行在同一个单元中。(如果 COM 组件设置为 MTA,则会发生跨封送处理。)
图 6. 跨单元封送处理的示例图
在上例中,不同单元内的调用将通过 COM 封送处理,而托管代码和非托管代码之间的调用将通过互操作封送处理。
结论
因此,.NET 应用程序和 COM 应用程序之间的通信通过 RCW 和 CCW 进行。
正如您所见,COM 应用程序可以实现 .NET 类型以实现类型兼容性,或者 .NET 类型可以实现 COM 接口以实现与相关 coclass 的二进制兼容性。
尽管托管客户端可以与非托管对象交互,但托管客户端期望非托管对象应像托管对象一样行事。
通过 COM 互操作针对非托管组件进行开发时,托管代码开发人员将无法使用 .NET 的某些功能,如参数化构造函数、静态方法、继承等。迁移现有组件或编写托管包装器将使托管代码开发人员更容易使用该组件。在某些情况下,开发人员希望将应用程序的部分迁移到 .NET,以便应用程序可以利用 .NET Framework 提供的新功能。例如,ASP .NET 提供了高级数据绑定、浏览器依赖的用户界面生成以及改进的配置和部署。设计人员应评估将这些新功能引入应用程序的价值何时会超过代码迁移的成本。