将 Visual J++ COM+ 组件迁移到 Visual J# 和 Enterprise Services
摘要:本文提供了使用 Visual J# .NET 将 COM+ 组件成功迁移到 .NET 框架所需要的信息。本文讨论了 Microsoft VM 与 Visual J++ 中当前提供了哪些 COM+ 支持,并且讨论了 .NET 框架和 Visual J# .NET 中提供的等效支持。
简介 Visual J++ 是一种不可思议的 COM+ 应用程序开发环境。该工具集与 Java 语言和 Microsoft 虚拟机 (VM) 的功能相配合,使开发人员能够快速生成强大的令人迷惑不解的应用程序。然而,随着 .NET 框架的出现以及 Microsoft VM 最终被逐步淘汰,许多开发人员正在寻找将其 Visual J++ COM+ 组件迁移到 .NET 框架的最佳途径。
到 EnterpriseServices 和 InteropServices 的映射
.NET 框架包含两个对于那些要迁移现有 Visual J++ COM+ 组件的开发人员特别有用的命名空间。System.Runtime.InteropServices 命名空间(此后称为 InteropServices)提供了负责 COM 运行库和公共语言运行库之间的互操作的类型。可以将该命名空间视为 com.ms.com 软件包和 Visual J++ 中大多数 @com 编译器指令的直接替代者。
System.EnterpriseServices 命名空间(此后称为 EnterpriseServices)由公开了 COM+ 所提供的服务的类型组成。可以将该命名空间视为 Microsoft VM 附带的 com.ms.mts 软件包以及 Visual J++ 中 @com.transaction 编译器指令的直接替代者。将这些命名空间和 Visual J# 结合使用与使用 Visual J++ 中现有支持之间的最重要差别在于:定义和注册类以充当 COM 服务器以及利用 COM+ 服务的方式是不同的。
@com.register 和 @com.transaction 属性 在 Visual J++ 内部,通过将 @com.register 和 @com.transaction 属性放在 Java 语言类的声明中,将该类公开为 COM+ 组件。@com.register 属性定义了以下元素:
l Coclass GUID
l 类型库 GUID
l 组件的 ProgID
该属性所提供的信息被插入到 Windows 注册表中,以便将 Java 语言类公开为 COM 服务器。@com.transaction 属性定义了类实例的事务要求。该值可以是 COM+ 所提供的任何事务支持级别(Required、Requires New、Supported 或 Not Supported)。
下面的类声明显示了这些属性的正确用法:
/** * @com.register ( clsid=FE9C2CAA-6232-4169-8911-B593D4E706DA, typelib=943A02F9-72C1-45C3-B057-D758357B4B4E, progid="VJServer.Server") * @com.transaction(required) */ public class Server {} 请注意,有几种将 Java 语言类公开为 COM+ 组件的不同方法。使用 @com.register 和 @com.transaction 属性只是代表了最常用的方案之一。
ServicedComponent 和 TransactionAttribute 在 .NET 框架内部,类必须扩展自 EnterpriseServices.ServicedComponent 类才能被公开为 COM+ 组件。还有其他达到此目标的方法,但这是利用 .NET 框架内的 COM+ 服务的建议方式。对这一公用超类的要求源自截获需要。通过从 ServicedComponent 进行扩展,对类实例的方法调用可以被公共语言运行库截获。然后,公共语言运行库可以调用 EnterpriseServices 命名空间中的类的实例,以提供您熟悉的 COM+ 服务。
要以编程方式定义事务支持级别,必须将 EnterpriseServices.TransactionAttribute 添加到类声明中。与 Visual J++ 中的 @com.transaction 一样,该属性定义了必须在其中激活类实例的事务上下文。这些属性之间的主要区别在于 .NET 框架中属性的类型安全性质以及 Visual J# 中新的属性语法。
以下类声明显示了应该如何在 .NET 框架中使用 Visual J# 来表示 Visual J++ Server 类:
import System.EnterpriseServices.*;
/**@attribute Transaction(TransactionOption.Required)*/ public class Server extends ServicedComponent {} 请注意 TransactionOption 枚举的用法,该枚举设置了 TransactionAttribute 内部的事务支持级别。该枚举以一种类型安全的方式定义了事务级别。此外,请注意在 TransactionAttribute 名称中省略了 Attribute 部分。Visual J# 编译器不要求您包含要添加到类中的属性的全名。
GuidAttribute 和 ProgIdAttribute 要设置 coclass CLSID 和 ProgID,请使用 InteropServices.GuidAttribute 和 InteropServices.ProgIdAttribute 属性。这两个属性的用途与 Visual J++ 中的 @com.register 属性完全相同。它们指定了应该用来将类作为 COM+ 组件向 COM 客户端公开的 GUID 和 ProgID。下面的代码显示了如何将这些属性添加到 Server 类:
import System.EnterpriseServices.*; import System.Runtime.InteropServices.*;
/**@attribute Transaction(TransactionOption.Required)*/ /**@attribute Guid("FE9C2CAA-6232-4169-8911-B593D4E706DA")*/ /**@attribute ProgId("VJServer.Server")*/ public class Server extends ServicedComponent {} 如果您忽略了这些属性,当您向 COM+ 注册该程序集时,.NET 框架将为您生成这些属性。也就是说,如果您打算支持现有的 COM 客户端,则应该明确设置这些属性。如果您计划以一种无缝的方式替换现有的 Visual J++ COM+ 组件,尤其应该这样做。
如果您只打算支持基于 .NET 框架的客户端,则可以依靠默认的生成过程。.NET 框架客户端使用 newobj 中间语言说明(Visual J# 中的新增功能)来创建类的实例。它们不使用 COM 客户端所使用的旧式 CoCreateInstance API 调用。
注册 目前,您已经知道如何创建能够镜像 Visual J++ COM+ 组件类的 Visual J# 类。它具有相同的 ProgID、相同的 CLSID 和相同的事务支持级别。剩下的唯一任务是讨论注册区别。完成这一任务之后,您就将了解有关如何迁移 Visual J++ COM+ 组件的要素。
假设您使用了 COM DLL 项目模板,您知道 Visual J++ 将在生成过程的结尾创建一个 COM DLL。该过程还生成一个类型库(它将嵌入到 DLL 中),并且将该 DLL 注册到 Windows 注册表(使用 DllRegisterServer 导出的实例,它由 Visual J++ 自动生成)。从此以后,生成的 COM DLL 就像又一个 COM 服务器。您可以将它复制到其他计算机并运行 regsvr32.exe(它调用 DllRegisterServer),然后从这一新位置使用 COM 服务器。
尽管该操作对于注册 COM 服务器已经足够,您仍然需要将该组件注册到 COM+ 目录。这可以通过各种方式来完成,包括使用 COM+ 管理 API 以及将该 DLL 从 Windows 资源管理器拖放到“组件服务”MMC 管理单元。无论如何,除非将该组件注册到 COM+,否则它将无法利用任何 COM+ 服务。
同样地,Visual J# 类也将需要注册到 Windows 注册表和 COM+ 目录。然而,Visual J# 在编译类后不会产生 COM DLL,而是产生一个 .NET 程序集。尽管该文件可能具有与 COM DLL 相同的扩展名,但它并不是 COM 组件。而且,尽管它也具有相同的可移植可执行 (PE) 文件格式,但该文件并不包含 COM DLL 所必需的关键性的 DllGetClassObject 导出。
.NET 框架 SDK 提供了一个与 regsvr32.exe 具有相同功能的工具。该工具称为 regasm.exe,它检查在给定 .NET 程序集中的类中找到的 GUID 和 ProgID 属性,并使用这些信息在 Windows 注册表中创建正确的条目。尽管这可以解决注册问题的第一部分,它并不能处理向 COM+ 进行注册的问题。遗憾的是,该过程并不像将文件拖放到 MMC 中那样简单。要解决注册问题的第二部分,我们需要放下正题,首先讨论一下 .NET 框架的一项基础功能。
强名称 公共语言运行库使用一种非常强大的类型加载器,它提供对类版本控制的支持。这是一项在 Microsoft VM 或任何其他 Java 语言虚拟机实现中没有提供的功能。简而言之,每个 .NET 程序集都在内部嵌入了版本信息。该信息用于确保程序集中类的使用者能够得到正确的版本,而不只是在搜索 classpath 时首先找到的任何类或 JAR 文件。
尽管这要比简单地使用类名(就像在 Java 语言中一样)好,但它仍然存在相同的根本问题:没有办法防止他人用其他具有相同名称和版本的程序集来运行和替换一个程序集。.NET 类型加载器根本不知道已经发生了这种切换,并且应用程序将无法按预期方式工作(更糟糕的是,新的程序集可能被用来以某种方式使用系统)。
.NET 框架中针对该问题的解决方案就是唯一地命名程序集。这被称为赋予程序集一个强名称。强名称由以下部分组成:
l 程序集名称
l 版本
l 区域性(如果提供)
l 一个公钥
l 一个数字签名
要创建强名称的程序集,需要具有一个加密密钥对(公钥和私钥)。公钥作为强名称的一部分嵌入到程序集文件中。然后使用私钥对该程序集的哈希进行签名。得到的数字签名也被嵌入到程序集文件中。.NET 框架 SDK 包含一个名为 sn.exe 的工具,它将创建可用来创建强名称程序集的加密密钥对。请使用 K 选项并指定要向其写入该密钥对的文件名(标准做法是使用 .snk 扩展名)。
有两个与该密钥文件有关的注意事项:
l 请确保它的安全。可以使用公钥在运行时授予安全权限。如果有人获得您的密钥对,就可以使用该密钥对来生成自己的程序集,并且在与您的程序集相同的信任级别下运行。
l 您不必为您希望赋予强名称的每个程序集生成新的加密密钥对。相反,您可以对您创建的所有程序集使用同一密钥对。例如,.NET 框架中的所有程序集就使用同一密钥对。这意味着它们的强名称都包含相同的公钥,并且它们都由同一私钥签名。您可能希望对您生成的程序集采用同样的方法。
在通过 sn.exe 创建了 .snk 文件之后,使用强名称生成程序集将是很简单的操作。需要做的全部工作就是向类文件中添加两个新属性,然后重新生成项目。下面的代码显示了这些添加操作:
import System.EnterpriseServices.*; import System.Runtime.InteropServices.*; import System.Runtime.CompilerServices.*; import System.Reflection.*;
/** @attribute Transaction(TransactionOption.Required) * @attribute Guid("FE9C2CAA-6232-4169-8911-B593D4E706DA") * @attribute ProgId("VJServer.Server") * @assembly AssemblyVersion("1.0.0.0") * @assembly AssemblyKeyFile("key.snk") */ public class Server extends ServicedComponent {} System.Reflection.AssemblyVersionAttribute 用于手动设置程序集版本号。如果您不提供该属性,Visual J# 将为您生成它。System.Runtime.CompilerServices.AssemblyKeyFileAttribute 包含由 sn.exe 实用工具生成的密钥文件的相对(或绝对)路径。在添加这些属性之后,只需要重新生成项目就可以得到强名称的程序集。
请注意用于定义程序集属性的新语法。因为这些属性适用于整个程序集,所以应使用 @assembly 声明而不是 @attribute。
有必要指出的是,当您创建新的 Visual J# 项目时,这些属性将被自动添加到项目中一个名为 AssemblyInfo.jsl 的独立文件中。这是所有 Microsoft 语言共有的一项标准约定(C# 使用 AssemblyInfo.cs,而 Visual Basic .NET 使用 AssemblyInfo.vb)。该文件包含与给定程序集而不是特定的类或接口相关联的所有属性。为简洁起见,上述示例没有使用该文件。只要在项目内部的某个源文件中找到这些属性,Visual J# 就产生一个强名称程序集。
全局程序集缓存与 Regsvc.exe 既然您有了强名称程序集,让我们返回到注册这一主题。您或许正在纳闷为什么我们需要如此详细地讨论强名称,其实原因非常简单。要将 .NET 程序集注册到 COM+ 目录,它必须具有强名称。因为 COM+ 目录是一种计算机范围的资源,所以每个程序集都具有唯一的名称是绝对必要的。
.NET 框架提供了两种不同的向 COM+ 目录注册 .NET 程序集的方式:动态注册和手动注册。这两种方法都只对含有从 EnterpriseServices.ServicedComponent 扩展的类的强名称程序集起作用。
在这两种方法中,动态注册机制是最简单的,也是最受限制的。当客户端创建扩展了 ServicedComponent(在您的情形中,为 Server 类)的类的实例时,运行库将验证该类是否已注册到 COM+ 目录。如果尚未注册,运行库会自动将该程序集同时注册到 Windows 注册表和 COM+ 目录中。该方法有两个注意事项:
l COM 客户端不能使用动态注册的程序集。
l 主要创建 ServicedComponent 类的新实例的线程标识必须是宿主该组件的计算机上的管理员。
手动注册涉及到其他开销,但它更为灵活。该方法消除了动态注册所具有的主要问题:缺少对 COM 客户端的支持。然而,该过程仍然要求使用本地计算机上的管理员帐户来执行注册。
要手动注册 .NET 程序集,需要使用 .NET 框架 SDK 中包含的其他两个实用工具:gacutil.exe 和 regsvc.exe。
Gacutil.exe 用于将程序集安装到全局程序集缓存 (GAC) 中。GAC 与 %WinDir%JavaClasses 目录等效,该目录含有可供类加载器访问的类。GAC 用于相同目的。如果某个程序集被安装到 GAC 中,则系统中运行的任何应用程序都可以访问它。
请注意,尽管您可以导航到 Assembly 来查看 GAC 中的程序集,但 GAC 实际上由多个不同的目录组成,这些目录以虚拟形式显示在 Windows 资源管理器视图中。这就是各个实用工具将程序集安装到 GAC 中而不是仅仅将程序集复制到该目录中的原因。
在将程序集放到 GAC 中以后,最后一步是使用 regsvc.exe 实用工具在 COM+ 目录中注册该程序集。Regsvc.exe 采用要注册的程序集的名称作为命令行参数。在被调用时,该工具将读取程序集的元数据,创建一个 COM+ 应用程序,并且将所有扩展自 EnterpriseServices.ServicedComponent 的类都注册到 COM+ 目录中。默认情况下,COM+ 应用程序的名称就是该程序集的名称(不带扩展名)。
迁移常见 COM+ 功能 完成注册以后,您现在应该知道如何将 Visual J++ COM+ 组件迁移到 .NET 框架和 Visual J# 了。然而,您很可能希望将复杂得多的 COM+ 组件迁移到 .NET 框架。为此,我们将继续演练最常用 COM+ 功能的迁移:
com.ms.mtx.Context 类 尽管 COM+ 提供了多种有用的服务,但最为常用的功能是声明性分布式事务。尽管您知道如何以声明方式在 Visual J# 类上设置事务支持级别(使用 Transaction 属性),您仍然需要了解如何对事务结果进行投票。在 Visual J++ 中,使用 com.ms.mtx.Context 类在运行时与 COM+ 基础结构进行交互。该类提供了相应的方法,以控制与 COM+ 所提供的事务上下文相关联的众所周知的“happy”和“done”位。下面的代码概括了在 Visual J++ 中如何规范化地使用该类对事务进行投票:
import com.ms.mtx.*; /** * @com.register ( clsid=FE9C2CAA-6232-4169-8911-B593D4E706DA, typelib=943A02F9-72C1-45C3-B057-D758357B4B4E, progid="VJServer.Server") * @com.transaction(required) */ public class Server { public void doWork() { try { // Do some database work here. // Set the happy and done bits. Context.setComplete(); } catch (Exception ex) { // Handle the exception here. // Set the unhappy and done bits. Context.setAbort(); }//try/catch
}//doWork
}//Server 基于以前的经验,您可能会推测 EnterpriseServices 命名空间包含某种与 Visual J++ 中的 Context 类等效的逻辑。如下面的代码所示,EnterpriseServices.ContextUtil 证明这种推测是正确的:
import System.EnterpriseServices.*; import System.Runtime.InteropServices.*; import System.Runtime.CompilerServices.*; import System.Reflection.*;
/** @attribute Transaction(TransactionOption.Required) * @attribute Guid("FE9C2CAA-6232-4169-8911-B593D4E706DA") * @attribute ProgId("VJServer.Server") * @assembly AssemblyVersion("1.0.0.0") * @assembly AssemblyKeyFile("key.snk") */ public class Server extends ServicedComponent { public void doWork() { try { // Do some database work here. // Set the happy and done bits. ContextUtil.SetComplete(); } catch (Exception ex) { // Handle the exception here. // Set the unhappy and done bits. ContextUtil.SetAbort(); }//try/catch }//doWork }//Server 这两者之间有着不可思议的相似性。实际上,假设您的代码与前面的 Visual J++ 示例类似,您应该能够通过查找和替换操作将您的代码迁移到 Visual J#。请将 Context 更改为 ContextUtil 并使该方法名称的首字母大写。
如果您更详细地分析 EnterpriseServices.ContextUtil,您将找到 com.ms.mtx.Context 类的大多数功能(略有修改)。尽管我们可以开发使方法一一对应的映射,但这一练习在很大程度上是多余的,因为区别主要在于命名更改。了解这一点之后,请参阅有关 ContextUtil 成员的文档。
com.ms.mtx.SecurityCallContext 类和 com.ms.mtx.SharedPropertyGroupManager 类 EnterpriseServices 命名空间包含 com.ms.mtx.SecurityCallContext 类和 com.ms.mtx.SharedPropertyGroupManager 类的直接替代类。这些新的类提供了与 Visual J++ 相同的基本功能,并且提供了对 COM+ 内部的新增功能的访问。虽然这使得将代码迁移到 Visual J# 变得很容易,这些替代类最好的方面在于它们的名称与原来的 Visual J++ 类完全相同:EnterpriseServices.SecurityCallContext 和 EnterpriseServices。SharedPropertyGroupManager。
至于 ContextUtil,我们可以提供一个方法映射表,但确定这两组类之间有哪些变化是相当无关紧要的。只要粗略地查看一下 SharedPropertyGroupManager 类,对于迁移代码而言就已经足够了。
com.ms.mtx.IObjectControl 接口 com.ms.mtx.IObjectControl 接口由完成以下工作的 Visual J++ COM+ 类实现:这些类定义了将要被 COM+ 运行库调用的特定于上下文的激活和停用函数(IObjectControl::Activate 和 IObjectControl::Deactivate)。该接口还定义了一个方法,以便控制实例池化(IObjectControl::CanBePooled)。虽然实现该接口在 Visual J++ 中是可选的,但却是常见的做法。
EnterpriseServices 没有定义直接替代该接口的接口,而是由可供扩展的 ServicedComponent 基类定义这些方法。您需要做的全部工作就是在子类中覆盖这三个方法,以便复制 Visual J++ 中的功能。迁移代码的工作可能就像在新的 Visual J# 类中执行复制并粘贴操作一样简单(因为方法签名相同)。
小结
正如您已经看到的,迁移现有的 Visual J++ COM+ 组件是一个相当简单的操作。尽管我们使用了一个微不足道的示例,但 Visual J# .NET 以及 InteropServices 和 EnterpriseServices 命名空间确实提供了快速地将现有 COM+ 组件成功迁移到 .NET 框架所需的全部基本功能。代码迁移的这一部分工作的最为困难的方面是需要努力了解新的设计时和运行时环境。阅读完本文之后,您应该朝着掌握 .NET 框架的方向迈进了一大步。
| 出处: 微软公司 日期: 2004-9-1 |
好:1 一般:0 差:0 |
|