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 表示全局汇编库的视图。
<img src=/article/UploadPic/20067195382254.gif>
图 4. 全局汇编库
共享名
共享名用于使严格的命名需求与共享汇编结合起来。共享名有三个目标:
名称唯一:共享汇编必须具有全局唯一的名称。
防止名称冒充:作为开发人员,不希望有人发布您的汇编的后继版本并假称它是您发布的,无论是意外还是故意的。
提供引用标识:当涉及引用一个汇编时,共享名用于保证载入的汇编来自所期望的发行者。
共享名使用公共密钥加密实现。通常,过程如下所示:汇编的编写者产生一对密钥(或使用已有的),标记包含专有密钥清单的文件,并给调用者提供公共密钥。当引用汇编时,调用者记录生成强名称专有密钥相应的公共密钥。图 5 略述了该过程在开发期间如何工作,包括密钥如何存储在元数据中及如何生成签名。
该方案是称为“Main”的汇编,它引用一个称为“MyLib”的汇编。MyLib 具有共享名。重要的步骤如下所述。
<img src=/article/UploadPic/20067195382415.gif>
图 5. 实现共享名的过程
开发者调用在密钥对中传递的编译器和一组汇编的源文件。密钥对通常由称为 SN 的 SDK 工具生成的。例如,下面的命令生成一个新的密钥对并保存的文件中:
Sn 杒 MyKey.snk
多数的编译器将汇编作为编辑步骤的一部分。下面是一个 C# 命令的例子,它接受密钥对并给汇编签名:
Csc /t:library math.cs /a.keyfile:MyKey.snk /a.version:1.0.0.0
当编译器生成汇编时,公共密钥作为汇编标识的一部分保存在清单中。包括公共密钥作为标识的一部分给汇编提供了全局唯一的名称。
汇编生成后,包含清单的文件由专有密钥标记。结果签名保存在文件中。
当编译器生成 Main 汇编时,MyLib 汇编的公共密钥就作为引用 MyLib 的一部分保存在 Main 的清单中。
在运行时,.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 是完全不同的与类装载器有关的标识)。包括作为标识的一部分的版本主要用来区分用于并行目的的汇编的版本。
开发人员和管理员必须理解版本号的结构,因为它是通用语言运行时间如何加强汇编之间的版本关系的关键。
<img src=/article/UploadPic/20067195382319.gif>
图 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 文件。本节通过描述通用语言运行时间查找汇编和应用版本策略经历的阶段将这些概念连接起来。
当通用语言运行时间遇到引用存储在元数据中的另一个汇编时,开始载入汇编的过程。根据引用,下面的步骤决定载入那个汇编的哪个版本:
参考应用程序说明文件查看是否指定了策略。如果是,以策略的信息修改原始文件。例如,如果引用指定版本 1.0.0.0 而应用程序说明策略文件指定版本 2.0.0.0,通用语言运行时间将按照如同初始指定版本 2.0.0.0 一样处理。
在应用程序目录(和子目录)中查找匹配的汇编。“匹配”定义为精确的主版本号和次版本号(除非 QFE 策略被禁止)。
不管通过查找是否发现一个匹配项,全局汇编库都将由 QFE 引用。这使管理员发布每个人都应该得到的故障排除。
最后,参考管理员策略文件。最后参考该文件是因为管理员最后决定载入哪个版本。
--------------------------------------------------------------------------------
发布
发布包含至少两个不同的方面:包装代码和将这些包装分布给运行该应用程序的客户端和服务器。.NET 框架主要的目标是通过毫无影响的安装简化发布和复制发布的可行性。汇编自描述的天性使我们摆脱对注册表的依赖,因此使安装、卸载和复制变得相当简单。但是,有几种场合下复制作为发布机制并不充分。在这些情况下,.NET 框架提供扩展代码下载服务并集成在 Windows 安装程序中。
包装
在 .NET 框架的第一个版本中有三个包装选项可用:
As-built(DLL 和 EXE)。在许多场合,不需要特别的包装。应用程序以发布工具制造的格式发布,即 DLL 和 EXE 的集合。
Cab 文件。为了更有效地下载,Cab 文件可用于压缩应用程序。
Windows 安装程序包。 Microsoft Visual Studio.NET 和其他安装工具允许建立 Windows 安装程序包(.msi 文件)。Windows 安装程序允许利用应用程序修复、选择安装以及其他 Microsoft Windows 2000 应用程序管理功能。
分布方案
.NET 应用程序能以多种方式发布,包括复制、代码下载以及通过 Windows 安装程序。
对于许多应用程序,包括 Web 应用程序和 Web 服务,发布和复制一组文件到磁盘并运行一样简单。卸载和复制就像删除这些文件或复制它们一样容易。
.NET 框架提供使用 Web 浏览器下载代码的支持。该部分有几个重要问题,包括:
零影响:没有注册表项添加到机器上。
增量下载:汇编的许多分块只有在引用时才下载。
下载与应用程序隔离:代表某个应用程序的下载代码不影响机器上其它的应用程序。支持代码下载的主要目的是:防止用户下载新版本的某个共享的组件影响浏览具体的 Web 站点和影响其他应用程序。
No Authenticode 对话框:访问安全系统的代码用于允许可移动代码以部分信任程度运行。将不再出现对话框询问用户选择是否信任该代码。
最后,.NET 完全集成到 Windows 安装程序和 Windows 2000 应用程序管理功能中。