Microsoft® .NET 框架介绍了几个新功能,旨在简化应用程序发布和解决 DLL Hell。最终用户和开发人员都熟悉版本和发布问题,这些问题会伴随着如今基于组件的系统一同出现。例如,每个最终用户都在他们的机器上安装了一个新的应用程序,没料到已有应用程序神秘地停止了工作。多数开发人员花费时间使用 Regedit,努力保持所有必要的注册项一致以便激活 COM 类。
.NET 框架中用于解决 DLL Hell 问题的设计原则和实现技术是建立在 Microsoft Windows® 2000 的基础上的, Rick Anderson 所著的 The End of DLL Hell(英文)和 David D'Souza, BJ Whalen 及 Peter Wilson 所著的 Implementing Side-by-Side Component Sharing in Applications (Expanded) (英文)中都有说明。.NET 框架提供的许多功能都在这两篇文章中有所描述,包括应用程序隔离和并行组件,用于建立在 .NET 平台的应用程序。您将了解 .NET 平台上提供的版本支持,它能使本地 Windows 应用程序更紧密地汇集。
本文介绍了汇编的概念,并描述 .NET 如何使用汇编来解决版本和发布问题。我们将特别讨论汇编如何构建,如何命名以及编译器和通用语言运行时如何使用汇编来记录和加强应用程序片段间的版本依赖。我们也将讨论应用程序和管理员如何能通过我们所称的版本策略定制版本行为。
介绍并说明了汇编后,将展示几个发布方案,以便使您对 .NET 框架中提供的各种打包和分发选项多少有一些了解。
问题叙述
版本
从客户的角度,最常见的版本问题就是我们所说的 DLL Hell 问题。简单地讲, DLL Hell 是指当多个应用程序试图共享一个公用组件(如某个动态连接库(DLL)或某个组件对象模型(COM)类)时所引发的一系列问题。最典型的情况是,某个应用程序将要安装一个新版本的共享组件,而该组件与机器上的现有版本不向后兼容。虽然刚安装的应用程序运行正常,但原来依赖前一版本共享组件的应用程序也许已无法再工作。在某些情况下,问题的起因更加难以预料。比如,当用户浏览某些 Web 站点时会同时下载某个 Microsoft ActiveX® 控件。如果下载该控件,它将替换机器上原有的任何版本的控件。如果机器上的某个应用程序恰好使用该控件,则很可能也会停止工作。
在许多情况下,用户需要很长时间才会发现应用程序已停止工作。结果往往很难记起是何时的机器变化影响到了该应用程序。用户可能会回忆起一周前安装了一些东西,但安装与目前看到的状态并没有任何明显的关联。 更糟的是,现在很少有诊断工具帮助用户(或帮助他们的技术支持人员)确定有什么问题。
这些问题的原因是应用程序不同组件的版本信息没有由系统记录或加强。而且,系统为某个应用程序所做的改变会影响机器上的所有应用程序—现在建立完全从变化中隔离出来的应用程序并不容易。
很难建立一个隔离应用程序的一个原因是当前运行时环境只允许单独版本组件或应用程序的安装。这个限制意味着组件的编写者必须以向后兼容的方式编写他们的代码,否则当他们安装新组件的时候会有终止已有应用程序的风险。实际上,如果可能的话,编写永远向后兼容的代码是非常难的。在 .NET 中,side by side 概念是版本问题的核心。"Side by side" 是在同一台机器上同时运行不同版本的相同组件的能力。使用支持并列的组件,编程人员不必努力维护严格的向后兼容,因为不同的应用程序自由使用某个共享组件的不同版本。
发布和安装
现在安装应用程序是多步过程。一般,安装一个应用程序包括复制许多软件组件到磁盘,和在系统中进行一系列描述那些组件的注册项。
注册表中的项和磁盘上文件的分隔使复制应用程序和卸载他们非常困难。而且,在注册表中完全描述某个 COM 类所需的许多项之间关系非常松散。这些项常常包括联合类、接口、类型库和 DCOM app ID 的项,不涉及任何放在注册表文档扩展或组件类别的项。要时常手工保持这些项的同步。
最后,需要该注册足迹激活任何 COM 类。这极大地复杂了发布分布式应用程序的过程,因为必须到每个客户端的机器进行适当的注册项。
如今另一个共同问题是:对一个正在运行的应用程序进行更新是不现实的。这是 Web 应用程序最大的问题,Web 应用程序必须停止工作然后重启动以更新应用程序使用的 COM 类。
这些问题主要由从组件自己分离传来的组件描述引起的。换句话说,应用程序不是自描述的和独立的。
解决方案的特性
.NET 框架必须提供以下基本的能力解决刚刚描述的问题:
应用程序必须是自描述的:自描述的应用程序去掉了对注册表的依赖,能够毫无影响的安装和简单的卸载和复制。
必须记录和加强版本信息:版本支持必须建立在平台内部以保证依赖的适当版本在运行时载入。
必须记得“上次已知的正确配置”:当应用程序成功运行时,平台必须提供记住这套一起工作的组件的能力—包括它们的版本—。
必须支持并列组件:允许多个版本的组件同时安装和运行在机器上,允许调用者指定他们需要载入的版本代替不知不觉被强迫的版本。.NET 框架通过允许框架自己的多个版本同时存在于一台单独的机器上使并列邻先了一步。这极大地简化了升级问题,因为管理员如果需要可以选择运行不同版本 .NET 框架上的不同应用程序。
必须使应用程序隔离: .NET 框架必须简化(实际上已经默认)编写不受机器上其他应用程序的改变影响的应用程序。
汇编:积木
汇编是 .NET 框架用于解决刚描述的版本和发布问题的积木。汇编是类型和资源的发布单元。在许多方面汇编和现在的 DLL 相同。从本质上讲,汇编是“逻辑 DLL”。
汇编是通过元数据调用清单自描述的。就像 .NET 使用元数据描述类型一样,它也使用元数据描述包含类型的汇编。
汇编不仅仅于发布有关。例如,.NET 中的版本在汇编层完成 —没有任何减少,就像一个模块或类型的版本化。而且,汇编还用于在应用程序之间共享代码。包含某个类型的汇编是该类型标志的一部分。
访问安全系统的代码在其许可模型的内核中使用汇编。汇编的编写者在清单中记录一组运行该代码所需求的许可,然后管理员将许可授权给基于汇编的代码,此汇编包含该代码。
最后,汇编也是类型系统和运行时间系统的核心,在其中他们为类型和服务建立了一个可视的边界作为解决引用类型的运行时间范围。
汇编清单
清单明确包括以下有关汇编数据:
标识:一个汇编标识由三部分组成:名称、版本号和选项文化。
文件列表:清单包括所有组成汇编的文件列表。对于每个文件,在建立清单时记录它的名称和内容的加密信息。该信息在运行时验证以确保发布单元的一致。
引用的汇编:汇编间的关系保存在收集的汇编清单中。从属信息包括版本号,它用于运行时保证载入正确版本的关系。
输出类型和资源:对类型和资源可用的可视选项包括“仅在我的汇编中可视”和“对我的汇编之外的调用者可视。”
许可需求:汇编许可需求分为三组:汇编运行需求、需要的但汇编还有一些即使没授权的功能的需求,以及编写者不想汇编被授权的需求。
IL 反汇编 (Ildasm) SDK 工具对于在汇编中查看代码和元数据很有帮助。图 1 是一个以 Ildasm 现实的范例清单。.assembly 表示汇编而 .assembly extern 包含有关其他汇编所依赖的信息。
<img src=http://www.newasp.net/Article/UploadPic/2005-4/200541065132983.gif>
图 1. 以 IL 反汇编显示的范例清单
汇编结构
到此为止,汇编主要以逻辑概念描述。本节通过描述他们如何在物理上体现帮助您使汇编更加具体。
通常,汇编由四个元素组成:汇编元数据(清单)、元数据描述类型、实现该类型的媒介语言 (IL) 代码和一组资源。不是所有的这些都出现在每个汇编中。只有清单是严格需要的,但类型或资源需要给汇编一些重要的功能。
有几个关于这四个元素能如何包装的选项。例如,图 2 表示包含整个汇编:清单、类型元数据、IL 代码和资源。
图 2. 包含所有汇编元素的 DLL
另一种情况,一个汇编的内容也许分割为多个文件。在图 3 中,作者选择将一些有用的代码分离到一个不同的 DLL 中,并在它的原始文件中保留一个大的资源文件(这里是一个 JPEG 文件)。这样做的一个原因就是优化代码的下载。.NET 框架只在引用时才下载文件,所以如果汇编包含经常被访问的代码或资源,那么将他们分成单独的文件将提高下载的效率。
图 3. 汇编元素分割为多个文件