65.9K
CodeProject 正在变化。 阅读更多。
Home

COM Interoperability in .NET Framework: Part I

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (14投票s)

2005年3月6日

5分钟阅读

viewsIcon

90801

downloadIcon

1225

在 .NET 框架中使用 COM 组件。

引言

在本文中,我们将回顾 COM 互操作性的概念,并通过一个演示 .NET 框架中 COM 互操作性的示例进行讲解。

互操作性的需求是什么?

COM 组件与 .NET 组件的内部架构不同,因此它们并非天生兼容。大多数基于 COM 对象构建其企业应用程序中间层服务的组织,无法放弃在这些解决方案上的投资。这些遗留组件应被 .NET 框架中的托管代码所利用。这就是互操作性发挥作用的地方;它是一个运行时可调用包装器 (Runtime Callable Wrapper, RCW),它将来自托管客户端的特定调用转换为对非托管 COM 组件的 COM 特定调用请求。对 RCW 的方法调用会使 .NET 组件认为它们只是在与另一个 .NET 组件通信。

在我们深入核心概念之前,先简单回顾一下 COM。

什么是 COM?

COM 是 Component Object Model 的缩写,它是一种用于代码重用的二进制规范。它为客户端代码与组件类通信的接口施加了标准。组件的 IUnknown 接口有助于维护使用该组件的客户端数量的引用计数。当此计数降至零时,组件将被卸载。所有组件都应实现 IUnknown 接口。引用计数通过 IUnknow::AddRef()IUnknow::Release() 方法进行维护,接口发现则通过 IUnknow::QueryInterface() 处理。

什么是运行时可调用包装器 (Runtime Callable Wrapper)?

.NET 应用程序通过一个称为运行时可调用包装器的托管包装器与 COM 组件通信。它充当非托管 COM 组件的托管代理。

当我们进行方法调用时,调用会到达 RCW 而不是对象本身。RCW 管理着 COM 组件的生命周期。

互操作中的组件绑定是如何进行的?

绑定是指客户端需要了解的关于对象的关于方法、属性、事件等方面的信息。在 .NET 互操作性中,我们仍然可以使用传统的绑定技术:早期绑定和晚期绑定。

  • 早期绑定:客户端从组件的类型库中获取编译时类型信息。
  • 晚期绑定:客户端缺乏对象的丰富类型信息,这些信息是在运行时才知道的。

如何实现 COM 互操作性?

我们可以通过以下步骤实现:

  • 从 COM 组件创建运行时可调用包装器。
  • 在项目中引用元数据程序集 DLL,并使用其方法和属性。

步骤 1

有两种方法可以生成托管元数据包装器:

  • 使用类型库导入器实用程序。
  • VS.NET IDE

类型库导入器 (tlbimp.exe) 是一个命令行工具,它将 COM 类型库中的 COM 特定类型定义转换为 .NET 包装程序集中的等效定义。默认情况下,该实用程序会将包装程序集的名称与 COM DLL 的名称相同。

上述示例使用 VS.NET 命令提示符下的以下语法,从 COM 组件 “InteropExample.dll” 生成名为 “InteropExampleRCW.dll” 的元数据程序集。

tlbimp  InteropExample.dll  /output:InteropExampleRCW.dll /verbose

注意:它会自动解析 COM 中的 ADODB 引用。通过 “out” 参数,我们可以指定所需的程序集名称。

类型库导入器会检查 COM DLL 的类型库,并将其中的信息转换为 .NET 格式。生成的元数据程序集包含包装类,这些包装类可用于任何 .NET 客户端,例如 C# Windows 客户端。RCW 在创建组件时即可在运行时创建,并且它像托管类型一样处理 COM 特定数据类型。

VS.NET IDE 也可帮助我们生成元数据程序集。

点击 Project -> Add reference -> COM 选项卡。

该选项卡列出了本地计算机上已注册的组件,选择所需的 COM DLL 并添加到选定组件列表中。VS.NET 会自动生成一个元数据程序集,并将该组件提供的类放入一个与 COM DLL 同名的命名空间中。

包装器程序集的结构是怎样的?

对于导入到包装程序集中的每个类,都会生成两个包装类。可以使用 VS.NET 命令提示符下的 MSIL 反汇编器实用程序 (ildasm.exe) 查看 COM 特定的信息。

ildasm InteropExampleRCW.dll

InteropExample.dll(在 VB 6.0 中开发)组件具有以下公共类:

  • 作者
  • 标题

生成的程序集包含四个类:

  • 作者
  • AuthorsClass
  • 标题
  • TitlesClass

第一个是与原始 COM 类具有相同 GUID 的接口,第二个是要实例化的具体类。具体类以 “Class” 后缀结尾。具体类实现了原始 COM 类支持的所有接口。

所有生成的类型都放置在单个命名空间 InteropExampleRCW 下。

第二步

在项目中引用元数据程序集 DLL,并使用其方法和属性。

创建一个新的 C# Windows 应用程序项目。将以下 LabelTextBoxDataGrid 控件拖到窗体上,并相应命名;窗体将如下所示:

导入所需的程序集。

using System.Data;
using System.Data.OleDb;

在相应的 click 事件中包含以下代码:

要从 AuthorClass 使用作者搜索,请为 “Search Author” 按钮的 click 事件编写如下代码:

private void btnSearchAuthors_Click(object sender, System.EventArgs e)
{
 //create an instance of AuthorsClass from wrapper assembly
 InteropExampleRCW.AuthorsClass myAuthorRCW = 
              new InteropExampleRCW.AuthorsClass();
 
 DataSet dsAuthorList=new DataSet("Authors");

 OleDbDataAdapter daAuthRecs=new OleDbDataAdapter();
 ADODB.Recordset rsAuthors=new ADODB.Recordset(); 
 
 rsAuthors=myAuthorRCW.GetAuthors(txtAuthors.Text.ToString()); 

//invoke method from the RCW
 daAuthRecs.Fill(dsAuthorList,rsAuthors,"Authors"); 

 dataGridAuthors.SetDataBinding(dsAuthorList,"Authors");  

}

要从 TitleClass 使用标题搜索,请为 “Search Title” 按钮的 click 事件编写代码:

private void btnSearchTitle_Click(object sender, System.EventArgs e)
{
  InteropExampleRCW.TitlesClass  myTitlesRCW = 
               new InteropExampleRCW.TitlesClass();

  DataSet dsTitleList=new DataSet("Titles");

  OleDbDataAdapter daTitleRecs=new OleDbDataAdapter();

  ADODB.Recordset rsTitles=new ADODB.Recordset(); 
  
  rsTitles=myTitlesRCW.GetTitles(txtTitle.Text.ToString()); 
  
  daTitleRecs.Fill(dsTitleList,rsTitles,"Titles"); 

  dataGridTitle.SetDataBinding(dsTitleList,"Titles");  
}

运行的输出将如下所示:

如何释放 COM 对象?

运行时可调用包装器本身是托管的,因此其生命周期由公共语言运行时 (Common Language Runtime) 控制。当垃圾回收器在 RCW 上调用 Finalize() 方法时,COM 组件将从内存中释放。内部,RCW 会调用 COM 对象上 IUknown 接口的 Release()

要显式将 COM 对象从内存中移除,请调用 System.Runtime.InteropServices 命名空间中 Marshal 类的静态方法。

using System.Runtime.InteropServices;
Marshal.ReleaseComObject(myAuthorRCW);
© . All rights reserved.