Scott Hanselman
Corillian Corporation
摘要:为 Microsoft .NET 编写的解决方案必须是 100% .NET 的吗?Scott Hanselman 考察了混合式托管-非托管解决方案是怎样真正成为标准的。
现在是 2004 年的春季。Microsoft® .NET Framework 当前包含在 Microsoft® Windows Server™ 2003 中,并且我们知道在即将问世的、代号为“Longhorn”的 Microsoft® Windows® 版本中的许多功能丰富的服务都将用托管 API 生成。许多人都曾问过以下问题:“Longhorn 是托管的吗?”,暗示如果该操作系统的庞大部分用托管代码实现,则会非常美妙。当他们听说该操作系统并非全部是托管的时候,他们感到某种程度上的失望。
Don Box 简单地论述道:“这无关紧要。真正要紧的是……主要的访问模式……是托管的。”难道我关心设备驱动程序是用托管代码还是用 C 编写的吗?不,我只关心它是否能够工作以及是否能够正常工作。
在开发领域,有越来越多的围绕“.NET 纯洁性”这一主题的讨论。在销售应用程序时,经常会有人提出以下问题:“您的应用程序是百分之百 .NET 的吗?”或者,“您的应用程序在多大比例上是 .NET 的?”在这些问题背后隐含着一个定性判断,而这通常是带有轻蔑意味的。
我已经在许多技术情况简介中听到很多 CTO 说:“我们计划将我们的整个系统移植到 .NET。”为什么要花费 18 个月的时间来转换您的应用程序,以便到达您已经处于的终点呢?
人们推断,完全用 .NET 编写的应用程序(大概与 COM 没有任何互操作,并且没有任何对 Win32 API 的直接调用)要比由多种技术组合而成的应用程序高级。很不幸,关于“尽可能少地使用互操作”这一目标的具有讽刺意味的事实是,我们在其之上进行构建的 .NET Framework 本身就是一个奇妙的互操作性示例。
.NET 代表在开发人员工作效率方面实现的令人难以置信的飞跃,并且为 Windows 平台提供的服务赋予了干净的、一致的外观。许多年以来,Windows 平台提供的接口集(统称为 Windows SDK)已经作为 DLL 中导出的“C”样式函数向开发人员公开,而最近这些年以来,则通过组件对象模型 (COM) 向开发人员公开。事情变得更为复杂,而且引入了新的访问模式和抽象概念。
MFC 采用与 Microsoft® Visual Basic® 6.0 视图或 Windows 模板库 (WTL) 视图不同的方式包装了 Win32。传统的 Microsoft® ASP 在 HTTP 之上没有任何事件处理模型,而 Visual Basic 鼓励人们双击并使他们的事件自动 接通。对于 .NET Framework 而言,许多不同的“访问模式”已经在一个编程模型下统一起来。存在一种访问模式;它就是位于 Windows 上的 .NET Framework 托管 API —“平台”。

图 1. .NET“平台”
.NET Framework 库
Windows 平台具有许多高级系统服务,这些服务通过几乎数以千计的 API 公开。这一大型功能库包括各种级别的丰富功能。低级 API 可以从磁盘上打开一个文件,而高级 API 可以播放音频文件。.NET Framework 的设计者希望在丰富的平台功能遗产之上创建一致的面向对象的外观。CLR 和 .NET Framework 协同工作以公开 Windows 平台内部的功能,包括那些以前可能隐藏在难以使用或很少为人所知的 API 中的功能。
尽管 CLR 提供了一种新的应用程序开发范型,但它并未关闭使用现有库的大门。CLR 向开发人员提供了互操作性服务,但这些服务的最大使用者当然是 .NET 类库本身(它们通过 .NET API 释放现有 Windows 平台功能)。

图 2. 平台调用模型
例如,当使用 .NET Framework 库类 System.Web.Mail.SmtpMail 发送电子邮件时,类库使用一个 Helper 类来提取现有的 CDO(协作数据对象)COM 库。这只是 .NET 库开发人员选择依靠成熟且可靠的现有库而不是从头编写代码的成千上万个示例中的一个。尽管该示例以及其他许多示例使用库,公共语言运行库在某些时刻还需要使用 Windows 内部 API。正如人们常说的那样,无论您正在从事什么样的开发,最终都必须有人调用 LoadLibrary()。
如果 Microsoft 能够真正地将计算机虚拟化,他们会忽视在 Windows 平台中进行的投资。
当然,设计人员应尽可能没有痛苦地向现有库进行迁移。他们已经通过以下选择做到了这一点:通过运行库和 COM 可调用包装进行的 NET/COM INTEROP;通过一种名为 P/Invoke(平台调用的简称)的技术接入标准 Win32 平台 API 的能力;以及其他选择。在编写 CLR 中承载的代码时,平台的大量资源切切实实地掌握在开发人员手中 — 运行库是透明的,而非虚拟的。这标志着平台的视图与其他虚拟机实现根本不同。
GDI(图形设备接口)就是几乎完全通过 P/Invoke 无缝地引入托管世界的基础非托管服务的一个完美示例。在表格中列出当前的“Windows 三大支柱”,以便提醒读者哪里仍在发生大多数非托管工作(无论读者的方法多么纯粹),将是有用的。
| DLL | 内容说明 |
|
Kernel32.dll |
包含用于非托管内存管理和资源处理的低级操作系统函数。 |
|
GDI32.dll |
用于绘图、字体管理和常规设备输出的图形设备接口 (GDI) 函数。 |
|
User32.dll |
用于消息处理、菜单和通信的 Windows 管理函数。 |
如果我希望使用 User32.dll 中的内部非托管 FindWindow 方法,我会向 C# 应用程序中添加一个声明,如下所示:
[DllImport("User32.dll")]
public static extern int FindWindow(string strClassName,
string strWindowName);
在我进行这一声明(它与典型 .NET 函数定义的唯一显著差别是使用了 DllImport 属性 — 或许还有使用了 static extern)之后,我就可以愉快地像调用其他任何方法一样调用该方法了。当然,围绕非托管代码和托管代码之间的封送处理,有一些窍门和困难,但一般来说,它完全能够正常工作。它能够正常工作的证据位于您自己的代码的调用堆栈中 — 该调用堆栈经常跳转到非托管支持 DLL 中,而您很少需要考虑这一点。
尽管只使用 .NET 创建新的应用程序可能在部署或营销领域(“marketecture”)领域带来一些好处,但与用 .NET 重写非 .NET 组件(这些旧式组件本来可以利用)的成本相比,这些好处可能并不值得付出这样大的代价。“纯粹”的 .NET 解决方案或者只能利用那些可以完全在运行库内部实现的功能,或者只能利用那些已经由基础类库(它本身使用 COM Interop 和 P/Invoke)公开的函数。
.NET Framework 库本身不是“纯粹的 .NET”,因为它使用每个机会来充分利用基础平台原语。
从这一新的见解来看,.NET 纯粹性的整个概念变得似是而非了。.NET Framework 当然是在 Windows 平台上创建业务组件的最佳方式,但用 .NET Framework 编写的任何应用程序都只是被提升到像基础 Windows 操作系统服务一样高的高度。
到底为什么要编写托管代码呢?
人们为什么应该在语言和环境的阶梯上向上迈进一步呢?原因是为了在另一个抽象级别生活和工作。当然,.NET CLR 提供了比 Visual Basic 6.0(甚至可能包括 C++)更好的面向对象的体验,但 Framework 的真正价值在于它能够有效地隐藏基础操作系统和系统 API,然后向多种语言公开这一新的抽象层。您当然仍将不会用托管代码编写实时设备驱动程序,但我相信到这个十年的中期,几乎所有具有一定重要性的、以业务为中心的新代码都将在托管环境中编写。(如果这尚未成为事实的话。)
CLR 和托管运行库致力于使开发人员摆脱与内存管理、低级 IO 和存储甚至有线协议有关的辛苦工作。在 C++ 时代,我们非常详细地讨论了“更靠近业务人员”和“更易于操作”的对象,但当涉及到序列化该对象、容易地远程处理该对象的数据以及将该对象保存到数据库时,我们的面向对象的幻想被打碎了。面向服务的体系结构 (SOA) 开始协调逻辑服务和它所处理的对象(消息)的职责,但如果没有可扩展的、完整的、普遍存在的元数据以及统一的类型系统,则这一进展将是不可行的。我们已经过于纠缠于细节。这就是编写托管代码不必费脑筋的原因。
公共语言运行库还是虚拟机?
人们经常将 .NET 公共语言运行库(即 CLR)直接与 Java™ 虚拟机进行比较。起初,存在许多明显的类似之处:两者都是提供组件容器的“托管”环境,两者都使用“部分考虑”的中间语言,两者都提供像垃圾回收和线程处理设施之类的低级服务。
尽管从表面上看,这些类似之处在语义上是正确的,但这两种实现在基本原理上截然不同。将 CLR 与虚拟机进行比较只在某种程度上是合理的 — 它们的体系结构目标是根本不同的。
Sun Microsystems™ 发起一个名为 100% Pure Java 的营销计划,如果代码可移植性和基础操作系统透明性是合乎需要的终点,这当然是适当的。但是,许多第三方 Java 应用程序服务器通过直接在它们的未由 Java 应用程序平台(Java 类库)公开的宿主操作系统增值服务中明智地使用“C”函数调用(通过 Java 本机接口,即 JNI)来建立竞争优势。调入到核心平台中是利用仅通过本机接口提供的基础功能的唯一方式。

图 3. Java“栈”
Java 虚拟机是一个真正的“虚拟机”,它的最终目标是将基础操作系统抽象化(虚拟化),并且提供一个理想化的(未必理想,但却是理想化的)开发环境。Java 虚拟机还与 API、Java 应用程序平台以及该虚拟机实现所提供的服务密切地统一起来。无论您在何处运行已编译的 Java 代码,都将在该虚拟机的上下文内运行,并且在表面上与所提供的 Java 平台 API 相链接。
.NET 公共语言运行库的命名很恰当,因为它更多地是作为语言运行库使用,而不是作为虚拟机使用。尽管 CLR 通过使用中间语言成功地抽象了基础硬件的各个方面,但当它与 .NET Framework API 库相结合时,它却与基础平台(即 Windows)密切结合在一起。CLR 向任何支持 .NET 的语言提供了 Windows 平台的所有功能。
还应该说明的是,在 .NET 托管环境中运行的所有代码实际上都是像本机指令一样编译(实时)运行,从而在灵活性和性能之间提供了平衡,对硬件进行了抽象,同时“以贴近本质的方式”运行。
“混合”解决方案提供了真正的解决方案
现有的许多大型应用程序都是用 Microsoft® Visual C++® 和 COM 编写的。它们“以贴近本质的方式”编写,以便充分利用本机 Windows 多线程处理和细粒度的(不是自动的)内存管理。但是,还可以用 .NET 语言(如 C# 或 Visual Basic .NET)编写新的业务组件。这样,现有系统就在其进程空间承载 .NET 公共语言运行库并且“互操作”。使用 COM Interop 的接口每个进程内调用只会引起 10 个到 40 个处理器指令的最低系统开销(计算单位可能有所不同)。
旧式应用程序内承载的 .NET 组件可以利用该应用程序的现有服务。较低级的开发人员功能(如内存管理、对象生存期和对象定位)由 CLR 提供,而较高级的、特定于生产和销售过程的业务功能则通过旧式应用程序公开。
更为有用的是,可以用像托管服务(还可能像面向服务或面向对象的 API)一样的新观点提供应用程序的现有非托管服务。该服务的内部工作方式最终是非托管的这一事实只是实现细节而已。
该“混合”模型可以(而且确实)提供 Windows 平台上的最佳解决方案,它通过 C++ 利用了高性能的低级 API,并且利用了高度组件化的 .NET Framework 功能。在企业将其工作中心转移到完全使用 .NET 进行开发的过程中,这些解决方案可以非常成功地工作。
相关书籍
| • | |
| • |
现实世界中的 .NET
Scott Hanselman 目前是 eFinance 倡导者 Corillian Corporation 的技术传教士和 .NET 架构师。他具有十年左右的用 C、C++、Visual Basic、COM 以及最近开始使用的 C# 和 .NET 开发软件的经验。Scott 在过去三年中已被聘请为 Microsoft Developer Network 在美国俄勒冈州波特兰市的“地区主管”,负责为波特兰市和西雅图市的 Developer Days 和 Visual Studio.NET Launch 开发内容以及发表演讲。他是 PacWest 地区的 Microsoft Events 的最高级演讲者,并且已经与人合著了两部著作(由 Wrox Press 出版)。Scott 和 Corillian 是 Web 服务互操作性组织 (WS-I) 的成员。
浙公网安备 33010602011771号