摘要: 本文介绍汇编概念并说明 .NET 框架如何使用汇编解决版本和发布问题。
目录
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 框架用于解决刚描述的版本和发布问题的积木。汇编是类型和资源的发布单元。在许多方面汇编和现在的 DLL 相同。从本质上讲,汇编是“逻辑 DLL”。
汇编是通过元数据调用清单自描述的。就像 .NET 使用元数据描述类型一样,它也使用元数据描述包含类型的汇编。
汇编不仅仅于发布有关。例如,.NET 中的版本在汇编层完成 —没有任何减少,就像一个模块或类型的版本化。而且,汇编还用于在应用程序之间共享代码。包含某个类型的汇编是该类型标志的一部分。
访问安全系统的代码在其许可模型的内核中使用汇编。汇编的编写者在清单中记录一组运行该代码所需求的许可,然后管理员将许可授权给基于汇编的代码,此汇编包含该代码。
最后,汇编也是类型系统和运行时间系统的核心,在其中他们为类型和服务建立了一个可视的边界作为解决引用类型的运行时间范围。
汇编清单
清单明确包括以下有关汇编数据:
IL 反汇编 (Ildasm) SDK 工具对于在汇编中查看代码和元数据很有帮助。图 1 是一个以 Ildasm 现实的范例清单。.assembly 表示汇编而 .assembly extern 包含有关其他汇编所依赖的信息。
图 1. 以 IL 反汇编显示的范例清单
汇编结构
到此为止,汇编主要以逻辑概念描述。本节通过描述他们如何在物理上体现帮助您使汇编更加具体。
通常,汇编由四个元素组成:汇编元数据(清单)、元数据描述类型、实现该类型的媒介语言 (IL) 代码和一组资源。不是所有的这些都出现在每个汇编中。只有清单是严格需要的,但类型或资源需要给汇编一些重要的功能。
有几个关于这四个元素能如何包装的选项。例如,图 2 表示包含整个汇编:清单、类型元数据、IL 代码和资源。
图 2. 包含所有汇编元素的 DLL
另一种情况,一个汇编的内容也许分割为多个文件。在图 3 中,作者选择将一些有用的代码分离到一个不同的 DLL 中,并在它的原始文件中保留一个大的资源文件(这里是一个 JPEG 文件)。这样做的一个原因就是优化代码的下载。.NET 框架只在引用时才下载文件,所以如果汇编包含经常被访问的代码或资源,那么将他们分成单独的文件将提高下载的效率。
图 3. 汇编元素分割为多个文件
DLL Hell 一个主要目的就是共享当前在基于组件的系统中使用的模型。默认情况下,单独的软件组件由机器上的多个应用程序共享。例如,每次一个安装程序复制一个 DLL 到系统目录或在 COM 注册表中注册一个类,该代码将潜在地影响其他运行在机器上的应用程序。实际上,如果一个已存在的应用程序使用共享组件的前一个版本,那么该应用程序将自动使用新版本。如果共享组件是严格向后兼容的这当然更好,但如果不可能,在许多情况下维护向后兼容是很困难的。如果没有维持向后兼容或不能维持,作为其他应用程序安装时的侧面影响经常导致应用程序中断。
.NET 设计方针的一个原则就是隔离组件(或汇编)。隔离一个汇编的意思是一个汇编只能由一个应用程序访问—不是由机器上的多个应用程序共享并且不可能因其他应用程序对系统的改变而影响。隔离赋予开发者对应用程序所用代码的绝对控制。隔离,或应用程序专用汇编期望在 .NET 应用程序中是默认的。隔离组件的趋势在 Microsoft Windows 2000 中随着 .local 文件的引入已经开始。该文件用于努力定位所需组件时使 OS Loader 和 COM 首先从应用程序目录查找。(请参阅 MSDN Library 中的相关文档,Implementing Side-by-Side Component Sharing in Applications(英文)。)
然而,有些情况下在应用程序之间共享汇编是必要的。很明显每个应用程序都有自己的 System.Winforms、System.ASP 或公用的 Web 表格控件的副本是没有意义的。
在 .NET 中,在应用程序之间共享代码是明确的决定。共享汇编需要一些附加的需求。特别是,共享汇编应该支持相同的汇编并排多个版本安装和运行在相同的机器上,或者甚至在相同的进程中,在相同的时间。另外,共享汇编有更严格的命名需要。例如,一个共享的汇编必须有一个全局唯一的名称。
隔离和共享的需要导致我们考虑两种汇编。这是个相当松散的集合,在这两种汇编之间没有实际的结构,但它们如何使用是不同的:专用于某个应用程序或与许多应用程序共享。
应用程序专用汇编
应用程序专用汇编是只对某个应用程序可视的汇编。我们期望这是 .NET 应用程序最普通的情况,因为 .NET 框架帮助建立从其它应用程序引起的系统变化中隔离的应用程序。
专用汇编的命名需求很简单:汇编名称必须在应用程序中是唯一的。没必要起全局唯一的名称。保持名称唯一不是问题因为应用程序开发者完全控制哪个汇编与应用程序隔离。
应用程序专用汇编部署在使用它们的应用程序目录结构中。专用汇编可以直接放在应用程序的目录或它的子目录中。通用语言运行时间通过称为 probing 的进程查找这些汇编。"Probing" 是汇编名称到包含清单的文件名称之间的简单映射。
特别地,通用语言运行时间把汇编的名称记录在汇编引用中,追加“.dll” 并在应用程序的目录中查找该文件。该方案中有一些变量,在那里运行时间会访问汇编命名的子目录中或汇编的风格命名的子目录。例如,某个开发者会选择将包含定位于德国的资源的汇编部署在称为“de”的子目录中,并将西班牙的资源部署在称为“es”的子目录中。
如前所述,每个汇编清单包括有关其关系的版本信息。该版本信息没有为专用汇编而加强,因为开发人员完全控制了部署到应用程序目录的汇编。
共享汇编
.NET 框架还支持共享汇编的概念。共享汇编是在机器上由多个应用程序使用的。使用 .NET,共享应用程序之间的代码是明确的决定。共享汇编有些额外的需求用于解决现在我们经历的共享问题。除了支持早先描述的并列之外,共享汇编还有许多严格的命名需求。例如,共享汇编必须有一个全局唯一的名称。而且系统必须提供“名称保护”—更确切的说,防止有人再使用编写者的汇编名称。例如,假设您是一个网格控件的厂家,并且发布了您的汇编版本 1。做为编写您需要确信没有其他人能发布声称为版本 2 的汇编或您的网格控件。.NET 框架支持通过称为共享名的技术支持支持这些命名需求。(在下一节详细说明)。
通常,应用程序编写者不对应用程序使用的共享汇编有同等程度的控制。结果,在每次引用共享汇编时都检查版本信息。另外,.NET 框架允许应用程序和管理员通过指定版本策略重载应用程序使用的共享汇编版本。
共享汇编通常部署到全局汇编库。全局汇编库是供多个应用程序使用的机器范围的汇编库。使用该库不是必要条件,但这样做有很多好处。例如,自动提供多个版本的汇编并行存储。而且,管理员能使用该库部署他们需要的每个机器上的应用程序要使用的缺陷修复或安全补丁。在该方案中,配置汇编到全局汇编存储能影响机器上的多个应用程序。.NET 框架利用版本政策(稍候描述)的概念解决了现在出现在系统中共享区域的问题,例如 %windir%\system32。
在库中添加汇编需要明确的管理员操作—实际上,安装过程必须有“管理员权限”。汇编从不在存储结束作为运行一个应用程序的侧面影响,也不是当前工作的共享汇编的任何存储。在 Visual Studo .NET 时间框架中,Windows 安装程序将更新为理解汇编和汇编库。这意味着可以使用 Windows 安装程序的所有功能,例如使用 .NET 应用程序选择安装和应用程序恢复。
.NET SDK 包括两个用于汇编库的工具。第一个是称为 AL 的工具,它允许在库中添加汇编。AL 使开发和测试方案变得方便,它不需要创建整个 Windows 安装程序包在库中添加一个汇编。使用 /install 开关在库中添加汇编:
Al /install:myassembly.dll
第二个工具是 Windows Shell Extension,它允许您使用 Windows Explorer 操作库。图 4 表示全局汇编库的视图。
图 4. 全局汇编库
共享名
共享名用于使严格的命名需求与共享汇编结合起来。共享名有三个目标:
共享名使用公共密钥加密实现。通常,过程如下所示:汇编的编写者产生一对密钥(或使用已有的),标记包含专有密钥清单的文件,并给调用者提供公共密钥。当引用汇编时,调用者记录生成强名称专有密钥相应的公共密钥。图 5 略述了该过程在开发期间如何工作,包括密钥如何存储在元数据中及如何生成签名。
该方案是称为“Main”的汇编,它引用一个称为“MyLib”的汇编。MyLib 具有共享名。重要的步骤如下所述。
图 5. 实现共享名的过程
Sn 杒 MyKey.snk
多数的编译器将汇编作为编辑步骤的一部分。下面是一个 C# 命令的例子,它接受密钥对并给汇编签名:
Csc /t:library math.cs /a.keyfile:MyKey.snk /a.version:1.0.0.0
在运行时,.NET 框架中有两个步骤保证共享名给予开发人员所需的利益。首先,在汇编安装到全局汇编库时,验证 MyLib 的共享名签名。(没有配置到库中的验证签名的选项也是可用的。)验证签名保证 MyLib 的内容从汇编建立以来没有改变。第二步是验证作为 Main 引用 MyLib 的一部分保存的公共密钥与 MyLib 身份的一部分的公共密钥相匹配。如果这些密钥相同,Main 的作者就能保证载入的 MyLib 版本来自同一个的发布者,该发布者编写了建立 Main 的 MyLib 版本。当涉及 Main 引用 MyLib 时,该密钥等效检查在运行时完成。
术语“签名”常常联想到 Microsoft Authenticode。理解共享名和 Authenticode 没有任何关系是非常重要的。这两个技术有不同的目标。实际上,Authenticode 意味着对发行者信任的水平,而共享名不是。没有与强名称相关联的授权许可或第三方签名授权。另外,共享名签名通常由编译器本身作为编译过程的一部分进行。但是,也有实用程序用于在 SDK 中签名。
另一个值得考虑的是“测试签名”工程。汇编的编写者经常不能访问需要完全签名的专有密钥。大多数公司很好的保护这些密钥的存储它只能被少数人访问。结果,.NET 框架提供少量在开发中的“测试签名”技术,然后再“真实签名”。
如刚刚所描述的,每个汇编清单记录它创建所依赖的每个关系的版本信息。但是,有一些方案应用程序的编写者或管理员需要在运行时以关系的不同版本运行。例如,管理员应该能发布故障排除版本而不需要重新编译每个应用程序以便得到该修改。而且,管理员必须能列出因发现安全漏洞或服务故障从没有使用的汇编的详细版本。.NET 框架通过版本策略在版本绑定中启用了该灵活性。
汇编版本号
每个汇编都有四个部分组成的版本号作为它标识的一部分(就是说,一些汇编的版本 1.0.0.0 和 版本 2.1.0.2 是完全不同的与类装载器有关的标识)。包括作为标识的一部分的版本主要用来区分用于并行目的的汇编的版本。
开发人员和管理员必须理解版本号的结构,因为它是通用语言运行时间如何加强汇编之间的版本关系的关键。
图 6. 汇编版本号的四个部分
版本号的几个部分是主要版本、次要版本、内部版本和修订版本。主要版本或次要版本的改变可认为是不可兼容的改变。例如,开发人员改变了一些方法参数的类型或彻底删除了一些类型。类装载器使用该信息使所依赖汇编的不兼容版本不默认载入。
另一方面,仅改变版本号中的建立和版本部分可认为是兼容的。这些改变一般是故障排除或安全补丁,在这种情况下类型定义没有在某种程度上中断调用者的改变,这是兼容的。这些兼容的变化经常称为 Quick Fix Engineering (QFE) 修复或动态修复。
默认的版本策略
当通用语言运行时间遇到在代码中引用汇编时,它决定载入所依赖汇编的版本。默认情况下,载入解决引用的汇编必须有与引用中的记录相同的主版本号和次版本号。如果这些号码不同,该汇编认为是不兼容的,并且不会默认载入。相反,通用语言运行时间会用最高的号码得到 QFE (或动态修复)。
例如,如果 Main 以 MyLib 的 1.0.0.0 版本编译,当运行发现 MyLib 的 1.0.1.1 版本时,将载入 1.0.1.1。
总是取最新的编译和版本的策略称为“自动 QFE 策略”。该原则的主要目的是允许管理员在不重新创建所有应用程序的情况下发布故障排除版本。
定制版本策略
有些时候前面描述的默认策略并不是您所要的。例如,也许应用程序被所安装的 QFE 不经意地中断了。
默认的版本策略能通过使用 XML 配置文件修改。关于版本,有两个文件:一个应用程序说明文件和一个机器范围或管理员文件。应用程序说明文件保存在与应用程序相同的目录中。该文件中的策略描述只影响该应用程序。机器范围策略文件当前保存在 Windows 目录。该文件被管理员用来描述影响本机器上所有应用程序的策略。例如,也许某个管理员确定了某个汇编的详细的版本具有一些安全漏洞,并且他确保该汇编将不再被使用。
自定义策略的示例包括:
绑定指定的版本
有时需要绑定一个与清单记录的版本完全不同的汇编。通过在配置文件中的<BindingPolicy>标签提供对该方案的支持。该策略用于映射某个关系指定版本的引用或某个关系所有版本的引用。
下面例子映射某个称为 Calcr 的汇编 6.0.0.0 版本的引用:
<BindingPolicy>
<BindingRedir Name="Calcr"
Originator="32ab4ba45e0a69a1"
Version="*" VersionNew="6.0.0.0"
UseLatestBuildRevision="yes"/>
</BindingPolicy>
关闭自动 QFE 策略
该策略允许停止“自动 QFE 策略”引用已提供的汇编。 <BindingPolicy> 标签也用在这里,但 UseLatestBuildRevision 属性设置为 No,如下面例子所示:
<BindingPolicy>
<BindingRedir Name="Calcr"
Originator="32ab4ba45e0a69a1"
Version="*" VersionNew="6.1.1212.14"
UseLatestBuildRevision="no"/>
</BindingPolicy>
安全模式
安全模式(或编译运行)策略用于恢复编译配置。启用该策略将使通用语言运行时间载入记录在清单中的关系的一个精确版本。大概应用程序在它创建、测试并第一次发布时工作。安全模式是用于恢复到该状态安全的网络。下面的 XML 代码为具体的应用程序打开安全模式:
<BindingMode>
<AppBindingMode Mode="safe"/>
</BindingMode>
如果某个具体的关系不符合版本规则或不经意引入一个错误,“安全模式”和“关闭自动 QFE 原则”可用于恢复应用程序到某个工作的状态。
策略解决方案中的阶段
本文已经介绍了几个版本和发布的概念,包括应用程序专用汇编、共享的汇编、全局汇编存储和用于指定版本策略的 XML 文件。本节通过描述通用语言运行时间查找汇编和应用版本策略经历的阶段将这些概念连接起来。
当通用语言运行时间遇到引用存储在元数据中的另一个汇编时,开始载入汇编的过程。根据引用,下面的步骤决定载入那个汇编的哪个版本:
发布包含至少两个不同的方面:包装代码和将这些包装分布给运行该应用程序的客户端和服务器。.NET 框架主要的目标是通过毫无影响的安装简化发布和复制发布的可行性。汇编自描述的天性使我们摆脱对注册表的依赖,因此使安装、卸载和复制变得相当简单。但是,有几种场合下复制作为发布机制并不充分。在这些情况下,.NET 框架提供扩展代码下载服务并集成在 Windows 安装程序中。
包装
在 .NET 框架的第一个版本中有三个包装选项可用:
分布方案
.NET 应用程序能以多种方式发布,包括复制、代码下载以及通过 Windows 安装程序。
对于许多应用程序,包括 Web 应用程序和 Web 服务,发布和复制一组文件到磁盘并运行一样简单。卸载和复制就像删除这些文件或复制它们一样容易。
.NET 框架提供使用 Web 浏览器下载代码的支持。该部分有几个重要问题,包括:
最后,.NET 完全集成到 Windows 安装程序和 Windows 2000 应用程序管理功能中。
.NET 框架能够零影响的安装和访问 DLL Hell。汇编是用于启用这些功能的自描述、版本发布单元。
创建隔离应用程序的能力是紧要的,因为它使创建的应用程序不受其他应用程序对系统的改变所影响。.NET 框架通过在应用程序的目录结构中发布应用程序专用汇编,促进了这种应用程序。
并行是 .NET 中共享和版本化问题的核心。并行允许某个汇编的多个版本同时在机器上安装和运行,并允许每个应用程序请求那个汇编的指定版本。
通用语言运行时间记录许多应用程序之间的版本信息,并在运行时使用该信息确保载入所依赖的适当版本。应用程序开发人员和管理员通过版本策略在选择载入的汇编版本方面提供灵活性。
.NET 提供几个包装和发布的选项,包括 Windows 安装程序、代码下载和简单复制。
本文地址:http://com.8s8s.com/it/it46400.htm