文件版本是一个很难解决的问题。实际上,如果仅仅在一个文件中将其某一位从0改变到1、或者从1改变到0,我们便不能绝对保证使用原来文件的代码和它使用新版文件时的行为一样。这是因为许多应用程序都会有意或者无意地引入bug。如果一个文件的后续版本修复了一个bug,应用程序便不再如预期那样运行。
这就存在一个问题:怎样在修复bug和增加特性的同时,还能保证不会损坏现有的应用程序?我曾经对这个问题思考了很久,并且得出了一个结论--那就是这是不可能的。很明显,这样的回答解决不了问题。应用程序文件总是会携带bug,开发人员也总是想增加新的特性。在对应用程序的正常运行抱有良好预期的同时,还必须采用另一种分发新文件的方式来保证一旦应用程序不能正常运行,我们仍然可以将它们轻易地恢复到最近一次的正常状态。
3.1 两种程序集、两种部署方式
.NET 框架支持两种程序集:弱命名程序集(weaklynamed assembly)和强命名程序集(stronglynamed assembly)。
重要 顺便说一句,在.NET框架的任何文档中都找不到“弱命名程序集”这个术语。为什么呢?因为这是我个人命名的。这里决定发明这个术语的目的是能使我们在谈论程序集时,不至于造成某种混淆。
弱命名程序集和强命名程序集在结构上是相同的,也就是说,它们使用同样的PE文件格式、PE表头、CLR表头、元数据,以及清单表。并且我们可以使用同样的工具(例如C#编译器和AL.exe)来生成两种程序集。二者之间的真正区别在于,强命名程序集有一个发布者的公钥/私钥对签名,其中的公钥/私钥对惟--地标识了程序集的发布者。利用公钥/私钥对,我们可以对程序集进行惟一的标识、实施安全策略和版本策略,从而可以将其部署在用户硬盘的任何地方、甚至互联网上。这种惟一标识程序集的能力使得应用程序在试图绑定一个强命名程序集时,CLR能够实施某些“已确知安全”的策略。
3.2 强命名程序集
如果-个程序集要被多个应用程序访问,那么这个程序集必须被放在一个CLR确知的目录下。
当CLR检测到该程序集被引用时,它会自动到这个目录下查找该程序集。
然而,这里有一个问题:两个(或多个)不同的公司可能会生产出有相同名称的程序集来。如果这些同名程序集都被复制到相同的目录下,最后一个安装的程序集将会替代前面的程序集,引用那些程序集的所有应用程序将不能再如期运行。(这实际上就是目前 Windows中出现 DLLhell 的原因。)
很明显,简单地用文件名来区分程序集是不够的。CLR需要支持某种机制来惟一地标识一个程序集。这就是所谓的强命名程序集。一个强命名程序集包含四个惟一标识程序集的特性:文件名(没有扩展名)、版本号、语言文化标识、和一个公有密钥标记(由公有密钥产生的一个值)。下面的字符串分别标识了四个不同的程序集文件:
"MyTypes,Version=1.0.8123.0,Culture=neutral,PublicKeyToken-b77a5c561934e089"
"MyTypes,Version=1.0.8123.0,Culture-"en-Us",PublicKeyToken-b77a5c561934e089"
"MyTypes,Version=2.0.1234.0,Culture=neutral,PublicKeyToken=b77a5c561934e089"
"MyTypes,Version=1.0.8123.0,Culture=neutral,PublicKeyToken-b03f5f7f11d50a3a"
其中第一个字符串标识了一个名为 MyTypes.dll 的程序集。它的版本号为 1.0.8123.0,并且没有设定任何特殊的语言文化,因为Culture被设为 neutral。当然,任何公司都可能生产一个名为 MyTypes.dll版本号为 1.0.8123.0,以及语言文化中性的程序集。
所以,必须有一种方式能够将一个公司的程序集和另一个公司的程序集区别开来。由于某些原因微软选择了标准的公钥/私钥对加密技术(其他标识惟一性的技术有 GUID、URL、URN等)。这种加密技术使得我们在程序集被安装到用户硬盘上的时候,能够校验其内比特位的完整性,并且还可以根据发布者的身份来授权某些许可。
这样,如果一个公司想惟一地标识它的程序集,那么它必须首先获取一个公钥/私钥对。然后将公有密钥和程序集相关联。不存在两个公司拥有同样的公钥/私钥对的情况,这种区分使得我们能够创建有着相同名称、版本、语言文化的程序集,而不引起任何冲突。
注意 利用 System.Reflection.AssemblyName 类,我们可以很容易地创建一个程序集名称,并获取一个程序集名称的各个部分。该类提供了几个公有实例属性,例如CultureInfo、FullNameKeyPair、Name,以及 Version。该类还提供了几个公有实例方法,例如 GetPublicKeyGetPublicKeyToken、SetPublicKey,以及SetPublicKeyToken。
一个弱命名程序集可以在其清单元数据中嵌入版本号和语言文化特性,但CLR总会忽略版本号,并且只有在搜寻子目录查找卫星程序集的时候才会使用其中的语言文化信息。因为弱命名程序集总是以私有方式部署的,所以CLR在程序集的基目录或任何子目录(在XML配置文件定位元素的privatePath属性中指定)中搜索程序集时,它只需利用该程序集的名称(加上扩展名.d1或者exe)就可以了。
一个强命名程序集包含有一个文件名、一个版本号以及一个语言文化信息。另外,强命名程序集还有一个发布者的私有密钥签名。
创建一个强命名程序集首先需要获取一个用强命名实用工具(Strong Name Utility,即SN.exe和.NET 框架 SDK,以及 Visual Srudio .NET 一起发布的一个工具)产生的密钥。该实用工具提供了整套的功能,我们可以根据命令行开关来指定它们。注意SN.exe的所有命令行开关都是区分大小写的。要产生一个公钥/私钥对,我们可以象下面这样运行SN.exe实用工具:
SN -k MyCompany.keys
该命令告诉 SN,exe 创建一个名为 MyCompany.keys的文件。MyCompany.keys 文件将包含一对以二进制格式存储的公有密钥和私有密钥。
公有密钥非常大。我们可以执行下面的命令来查看它们:
SN tp MyCompany.keys
(译注:上述查看