利用.NET框架简化发布和解决DLL Hell问题(2)
版本与共享
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=http://www.microsoft.com/china/msdn/images/dplywithnet04.gif>
图 4. 全局汇编库
共享名
共享名用于使严格的命名需求与共享汇编结合起来。共享名有三个目标:
名称唯一:共享汇编必须具有全局唯一的名称。
防止名称冒充:作为开发人员,不希望有人发布您的汇编的后继版本并假称它是您发布的,无论是意外还是故意的。
提供引用标识:当涉及引用一个汇编时,共享名用于保证载入的汇编来自所期望的发行者。
共享名使用公共密钥加密实现。通常,过程如下所示:汇编的编写者产生一对密钥(或使用已有的),标记包含专有密钥清单的文件,并给调用者提供公共密钥。当引用汇编时,调用者记录生成强名称专有密钥相应的公共密钥。图 5 略述了该过程在开发期间如何工作,包括密钥如何存储在元数据中及如何生成签名。
该方案是称为“Main”的汇编,它引用一个称为“MyLib”的汇编。MyLib 具有共享名。重要的步骤如下所述。
<img src=http://www.microsoft.com/china/msdn/images/dplywithnet05.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 是完全不同的与类装载器有关的标识)。包括作为标识的一部分的版本主要用来区分用于并行目的的汇编