从 COM 组件调用 .NET 组件务
升级到 Microsoft .NET
Mike Gunderloy
Lark Group, Inc.
2002 年 1 月
摘要:逐步讲解如何从 COM 客户端调用 Microsoft .NET 服务器。(12 页打印页)
目标
• |
理解 COM 可调用包装的概念 |
• |
创建可从 Microsoft庐 Visual Basic庐 6.0 调用的 .NET 服务器 |
• |
使用 sn、regasm 和 gacutil 实用工具 |
• |
编写使用 .NET 类的 Visual Basic 6.0 代码 |
假设
如果您具备如下条件,本文档将发挥最大功效:
• |
熟悉 Visual Basic 编程 |
• |
熟悉 COM 概念 |
• |
有使用 Visual Basic .NET 的权限 |
• |
理解 .NET 的整个结构 |
• |
理解如何在 Visual Basic .NET 中创建公共类 |
本页内容
有时,编程革命使我们被迫放弃以前所有的东西。举一个极端的例子,假设您现在已经编写了好几年的 Visual Basic 应用程序。如果像很多开发人员一样,您就会在那段时间内积累大量的代码清单。而且,如果您已经采纳了众多语言权威的建议,该代码就是组件化的。也就是说,借助于 COM(组件对象模型) — 以前的 Microsoft庐 ActiveX 服务器,将应用程序划分为多个可调用的功能块。当然,您还可能在来自其他开发人员和其他公司的组件(例如,ActiveX 控件)上有大量投入。
但是,如果您决定彻底将开发完全迁移到另一个操作系统,怎么办?这时,在 COM 上的全部投入都变得毫无价值。无法使用任何现有代码,而且不得不学习如何在新平台上做每件事。这对您的工作效率无疑是一个沉重的打击。
幸运的是,从 COM 到 .NET 的迁移涉及不到如此重大的工作效率损失。有两个关键概念,使 COM 开发到 .NET 开发的迁移容易得多,而且没有代码库或工作效率的任何损失:
• |
.NET 组件可以调用 COM 组件。 |
• |
COM 组件可以调用 .NET 组件。 |
这种双向互操作性是从 COM 向 .NET 迁移的关键。当了解了 .NET 的复杂性时,可以继续使用 COM 组件。在很多情况下,这种互操作性都很有用:
• |
向 .NET 的迁移不能立刻完成。学习 .NET 编程的概念和实现需要时间,所以当您、您的同事及供应商赶时间完成任务时,您或许会发现需要继续使用 COM 代码。 |
• |
无法同时迁移可迁移到 .NET 的代码。需要对每个迁移的组件单独进行迁移并测试。 |
• |
您也许正使用一些无法转换到 .NET 的第三方 COM 组件,并且供应商还未发布 .NET 版本。 |
• |
尽管 Visual Basic 6.0 代码能迁移到 .NET,但迁移得并不理想。也许有些组件因为实现或语言问题而不能迁移到 .NET。 |
通过本文,您将了解从 COM 客户端调用 .NET 服务器的细节。通过本系列的另一篇文章 Calling COM Components from .NET Clients,您将了解反向的调用,即从 .NET 客户端到 COM 服务器。
尽管 COM 客户端可调用由 .NET 服务器的公共类公开的代码,COM 客户端却不能直接访问 .NET 代码。为了从 COM 客户端使用 .NET 代码,需要创建一个称为 COM 可调用包装 (CCW) 的代理。本节将学习 CCW 结构,以及创建和部署将被 COM 客户端使用的 .NET 类的必要步骤。
COM 可调用包装
在 .NET 公共语言运行库 (CLR) 内部运行的代码称作托管代码。此代码能访问 CLR 提供的所有现成服务,例如,跨语言集成、安全性和版本支持,以及垃圾回收。不在 CLR 内部运行的代码称作非托管代码。因为 COM 是在 CLR 出现之前设计的,而且 COM 代码不在 CLR 提供的基础结构内部运行,所以它不能使用任何 CLR 服务。根据定义,所有 COM 组件都是非托管代码。
托管代码组件不仅依赖于 CLR,而且它们要求与之交互的组件也要依赖于 CLR。由于 COM 组件不在 CLR 内部运行,所以它们不能直接调用托管代码组件。非托管代码只是无法进入 CLR 内部直接调用托管组件。
摆脱这种窘境的办法就是使用代理。概括地说,代理是一套软件,它接受来自组件的命令,将这些命令修改并传递给另外一个组件。非托管代码调用托管代码中用到的特定类型的代理称作 COM 可调用包装或 CCW。图 1 以图解的形式显示了 CCW 如何跨越托管和非托管代码之间的边界。该图包含一个名为 ComUI.exe 的 COM 程序、两个名为 NETService.dll 和 Utility.dll 的 .NET 组件、以及连接它们的必要技术。
图 1. 用 CCW 调用托管代码
COM 可调用类的先决条件
当创建由 COM 客户端使用的 .NET 类时,要记住有两个先决条件。
首先,在 Visual Basic .NET 代码中,显式定义一个接口,并使上述类实现该接口。例如,下面的代码片段定义了一个名为 iFile 的接口和一个实现该接口的类:
Public Interface iFile
Property Length() As Integer
End Interface
Public Class TextFile
Implements iFile
' details omitted
End Class
对于 COM 客户端,通过接口实现功能有一个很大的好处。生成 CCW 时,.NET 与以前版本保持接口一致。这有助于避免 .NET 服务器的更改破坏 COM 客户端。
其次,任何对于 COM 客户端可见的类都必须声明为公共类。创建 CCW 的工具只定义基于公共类的类型。相同的规则也适用于将被 COM 客户端使用的方法、属性和事件。
对于那些包含将被 COM 使用的类的 .NET 程序集,还应该考虑用密钥对签名。Microsoft 将这称为用强名称 给程序集签名。用强名称给程序集签名有助于 .NET 确保程序集中的代码在程序集发布之后未被更改过。尽管未签名的程序集也能被 COM 客户端调用,但是签名对所有全局程序集 是必需的(全局程序集是将被多个客户端共享的程序集)。
注 通过将程序集作为私有程序集 直接部署到 COM 客户端的目录中,也可以从 COM 客户端使用未签名的程序集。因为全局程序集与大多数 COM 应用程序结构的兼容性比私有程序集更好,所以本文不涉及私有程序集的方法。
给所有程序集签名,甚至给私有程序集签名,是个很好的习惯。这将有助于为托管类生成更好的 CLSID,而且有助于避免不同程序集中的类之间的冲突。
用 sn 工具可以创建强名称。这个命令行工具有很多选项,在命令提示符处输入 sn /? 可以查看所有选项。给程序集签名需要用到的选项是 -k,它创建一个密钥文件。默认情况下,密钥文件使用扩展名 .snk。例如,要创建一个名为 NETServer.snk 的密钥文件,可以使用如下命令行:
sn -k NETServer.snk
为 COM 访问部署应用程序
创建一个包含将被 COM 客户端调用的类的 .NET 程序集之后,有三个步骤使 COM 可以使用这个类。
第一,必须为程序集创建一个类型库。类型库是 .NET 程序集内所包含元数据的 COM 等价物。类型库通常包含在扩展名为 .tlb 的文件中。类型库包含一些必要信息,这些信息允许 COM 客户端确定哪些类位于某个特定的服务器中,以及那些类支持的方法、属性和事件。.NET 框架 SDK 包含一个名为 tlbexp(类型库导出程序)的工具,它可以从程序集创建类型库。Tlbexp 包含许多选项,在命令提示符处输入 tlbexp /? 可以查看所有选项。这些选项其中之一是 /out 选项,它使您可以指定生成的类型库的名称。(如果不选择创建自己的名称,就会自动创建一个。)例如,要将名为 NETServer.dll 的程序集中的元数据提取到名为 NETServer.tlb 的类型库中,可以使用如下命令行:
tlbexp NETServer.dll /out:NETServer.tlb
第二,应该使用 .NET 框架 SDK 中的程序集注册工具 (regasm),在一次操作中创建并注册类型库。程序集注册工具是在一台计算机上同时开发 .NET 和 COM 时最易用的工具。就像 tlbexp 一样,regasm 有许多选项;在命令提示符处输入 regasm /? 可以查看所有选项。要用 regasm 创建并注册类型库,请使用如下命令行:
regasm /tlb:NETServer.tlb NETServer.dll
第三,必须将 .NET 程序集安装到全局程序集缓存 (GAC) 中,这样,它才能作为一个共享程序集使用。要将程序集安装到 GAC,请使用 gacutil 工具:
gacutil /i NETServer.dll
同样,在命令提示符处输入 gacutil /? 就可以得到 gacutil 所有选项的列表。
在如下示例中,将从 COM 代码中使用 .NET 组件中的属性和方法。将使用 regasm 从 .NET 程序集创建类型库并注册程序集,用 gacutil 使该程序集能全局可用。然后将看到如何从 Visual Basic 6.0 COM 代码中使用该 .NET 程序集。
创建 .NET 程序集
要创建包含公共类的 .NET 程序集,按下列步骤进行:
• |
打开 Microsoft庐 Visual Studio庐 .NET,在起始页上单击 New Project 。 |
• |
在屏幕左边的树视图中选择 Visual Basic Project 。 |
• |
选择 Class Library 作为项目模板。 |
• |
将应用程序名称设为 PhysServer2,单击 OK 创建此项目。 |
• |
在 Solution Explorer 窗口中突出显示名为 Class1.vb 的类,并将它重命名为 NETTemperature.vb。 |
• |
在 NETTemperature.vb(它将是一个空类定义)中选择 Class1 的代码,并将它替换为如下代码: Public Interface iTemperature Property Celsius() As Double Property Fahrenheit() As Double Function GetCelsius() As Double Function GetFahrenheit() As Double End Interface Public Class NET_Temperature Implements iTemperature Private mdblCelsius As Double Private mdblFahrenheit As Double Public Property Celsius() As Double _ Implements iTemperature.Celsius Get Celsius = mdblCelsius End Get Set(ByVal Value As Double) mdblCelsius = Value mdblFahrenheit = ((Value * 9) / 5) + 32 End Set End Property Public Property Fahrenheit() As Double _ Implements iTemperature.Fahrenheit Get Fahrenheit = mdblFahrenheit End Get Set(ByVal Value As Double) mdblFahrenheit = Value mdblCelsius = ((Value - 32) * 5) / 9 End Set End Property Public Function GetCelsius() As Double _ Implements iTemperature.GetCelsius GetCelsius = mdblCelsius End Function Public Function GetFahrenheit() As Double _ Implements iTemperature.GetFahrenheit GetFahrenheit = mdblFahrenheit End Function End Class |
这些代码以定义一个名为 iTemperature 的接口开始。由于这个接口是用 Public 关键字定义的,所以它将导出到将由该程序集创建的类型库中。可以将接口定义想像为全部或部分类定义的主干。就像类一样,接口定义可以包含成员(属性、方法 — 函数或子过程 — 和事件)。但是和类不同的是,接口定义不包含这些成员的任何代码。类可以实现一个接口(如同此示例)或多个接口。
这些代码定义了使用接口 iTemperature 的类 NET_Temperature。特别是,类定义中的这一行在类和接口之间建立协定:
Implements iTemperature
根据协定,类将实现接口的所有成员。还可以包含不是接口组成部分的附加成员,但是如果尝试建立没有完全实现接口的类,就会产生错误。
注 类公开了两个公共属性和两个公共方法。(有关创建类、方法和属性的基础知识,请参阅 Creating Classes in Visual Basic .NET)。
注意类成员和类实现的接口成员之间建立联系所用的语法。例如,NET_Temperature 类的 Celsius 属性是用这种方式定义的:
Public Property Celsius() As Double _
Implements iTemperature.Celsius
这行代码定义了一个返回 Double 的属性,并告诉编译器这个属性是 iTemperature 接口中 Celsius 属性的一个实现。
创建密钥对并给程序集签名
要使程序集能全局使用,需要创建密钥对并用它给程序集签名。另外,添加标题和描述信息能使程序集更易于使用。
要给程序集签名,可以运行 sn 实用工具并手工添加密钥文件的名称,或者用 Visual Studio .NET 用户界面生成强名称。我们将使用第二个方法。为此,按下列步骤进行:
• |
在 Visual Studio .NET 的 Solution Explorer 中,双击 AssemblyInfo.vb 文件,在编辑窗口中打开。 |
• |
在该文件顶部的 Assembly Attributes 一节中,将 AssemblyTitle 和 AssemblyDescription 行修改为: <Assembly: AssemblyTitle("PhysServer2")> <Assembly: AssemblyDescription(".NET Version of PhysServer")> 提示! Visual Basic .NET 中的 Assembly Attributes 与 Visual Basic 6.0 中的 Project Properties 等效。 |
• |
在 Solution Explorer 中,右击项目节点并选择 Properties。单击 Common Properties 文件夹,然后单击 Strong Name 属性页。选择标记为 Generate Strong Name Using 的框。单击 Generate Key 生成密钥文件,并将它加入项目。单击 OK 关闭属性对话框。 |
现在已经准备好创建程序集了。单击 Build 或按 Ctrl+Shift+B 键,生成程序集。
注册程序集并创建类型库
现在,可以从另外一个 .NET 应用程序使用这个新程序集和 NET_Temperature 类。但是,还必须使 COM 应用程序能够使用该类及其成员。打开一个 Visual Studio .NET 命令提示符(依次单击 Start、Programs、Microsoft Visual Studio .NET 7.0、Visual Studio .NET Tools 及 Visual Studio .NET Command Prompt),更改到 PhysServer2 的项目目录,并键入:
regasm /tlb:PhysServer2.tlb PhysServer2.dll
regasm 实用工具将创建类型库并注册到 Windows 注册表,使 PhysServer2.dll 中的类能被 COM 客户端使用。
将程序集添加到全局程序集缓存中
最后,要想使所有 COM 客户端不论在硬盘的什么位置都可以全局使用新注册的程序集,切换回 Visual Studio .NET 命令提示符并键入:
gacutil /I PhysServer2.dll
gacutil 实用工具将将程序集添加到 GAC 中,并打印一条状态消息告诉用户已经完成。
编写调用 .NET 类的 Visual Basic 6.0 代码
现在已经准备好编写 COM 客户端以使用 NET_Temperature 类了。按下列步骤进行:
• |
打开 Visual Basic 6.0,在 New Project 对话框中单击 New tab。 |
|||||||||||||||||||||||||||||||||||||||
• |
选择 Standard EXE 并单击 Open。 |
|||||||||||||||||||||||||||||||||||||||
• |
在 Project Explorer 窗口中突出显示名为 Form1 的窗体,并将它重命名为 frmTemperature。 |
|||||||||||||||||||||||||||||||||||||||
• |
通过添加适当控件并设置这些控件的属性(如表 1 所列),创建图 2 中所示的窗体。 表 1. frmTemperature 的控件
图 2. 测试窗体设计 |
|||||||||||||||||||||||||||||||||||||||
• |
要通过 CCW 使用 PhysServer2 中的类,单击 Project,然后单击 References 打开 References 对话框。为 .NET 版 PhysServer 选择引用,如图 3 所示,单击 OK 关闭对话框。 图 3. 设置对 .NET 组件的引用 |
现在可以编写使用 NET_Temperature 类的方法和属性的代码了。在 View 菜单中,单击 Code,并在 frmTemperature 的窗体模块中输入如下代码:
Private moTempClass As PhysServer2.NET_Temperature
Private moTemp As PhysServer2.iTemperature
Private Sub cmdConvertToC_Click()
With moTemp
.Fahrenheit = txtFahrenheit.Text
txtCelsius.Text = .GetCelsius
End With
End Sub
Private Sub cmdConvertToF_Click()
With moTemp
.Celsius = txtCelsius.Text
txtFahrenheit.Text = .GetFahrenheit
End With
End Sub
Private Sub Form_Load()
Set moTempClass = New PhysServer2.NET_Temperature
Set moTemp = moTempClass
End Sub
记住,在 .NET 项目中使用 iTemperature 接口定义 Net_Temperature 类。这些代码演示了如何从对象检索回接口(用名为 moTemp 的对象表示)。虽然这看起来像多余代码,但是如果用 Visual Basic 做试验,你会发现使用接口比使用对象方便得多。那是因为接口支持 Microsoft IntelliSense 命令完成。
试验
要查看起作用的 NET_Temperature 类,按下列步骤进行:
• |
按 F5 启动项目。 |
• |
在 Fahrenheit 文本框中输入 95 并单击 Convert to C。Celsius 框应该填写值 35。 |
• |
在 Celsius 框中输入 -14 并单击 Convert to F。Fahrenheit 框应该填写值 6.8。 |
• |
关闭窗体以停止项目。 |
回复Comments
{commenttime}{commentauthor}
{CommentUrl}
{commentcontent}