CLR via C#-----共享程序集和强命名程序集

1.两种程序集,两种部署

CLR支持两种程序集:弱命名程序集(weakly named assembly)和强命名程序集(strongly named assembly)。

"弱命名程序集"为作者自创

弱命名和强命名程序集结构完全相同。两者真正的区别在于,强命名程序集使用发布者的公钥/私钥进行了签名。这一对密钥允许对程序集进行唯一性的标识,保护和版本控制,并允许程序集部署到用户机器的任何地方,甚至可以部署到Internet上,由于程序集被唯一性的标识,所以当应用程序绑定到强命名程序集时,CLR可以应用一些已知的安全的策略。
<弱命名和强命名程序集的部署方式>

程序集种类可以私有部署可以全局部署
弱命名
强命名

2.为程序集分配强名称

要由多个应用程序访问的程序集必须放到公认的目录。只根据文件名来区分程序集明显不够,CLR必须支持对程序集进行唯一性标识的机制,这就是所谓的"强命名程序集"。强命名程序集具有4个重要特性,它们共同对程序集进行唯一性标识:文件名(不计扩展名),版本号,语言文化和公钥。由于公钥数字很大,所以经常使用从公钥派生的小哈希值,称为公钥标记(public key token)。一个公司要想唯一性标识自己的程序集,必须创建一对公钥/私钥。
创建强命名程序集的第一步是用.Net Framework SDK和Microsoft Visual Studio随带的StrongName实用程序(SN.exe)获取密钥。
生成密钥

SN -k MyCompany.snk

创建.snk文件后可再次实用SN.exe查看实际公钥,这需要执行两次SN.exe。第一次用-p开关创建只含公钥的文件(MyCompany.PublicKey)

SN -p MyCompany.snk MyCompany.PublicKey sha256

第二次用-tp开关执行,传递只含公钥的文件

SN -tp MyCompany.PublicKey

结果为
命令行工具要用管理员运行
知道了如何创建公钥/私钥对,创建强命名程序集就简单了。编译程序集时使用/keyfile:编译器开关

csc /keyfile:MyCompany.snk Program.cs

C#编译器只能对含清单的程序集文件进行签名
"对文件进行签名"的准确含义是:生成强命名程序集时,程序集的FileDef清单元数据表列出构成程序集的所有文件。每将一个文件名添加到清单,都对文件内容进行哈希处理。哈希值和文件名一道存储到FileDef表中。要覆盖哈希算法,可使用AL.exe的/algid开关,或者在程序集的某个源代码文件中,在assembly这一级上应用定制特性System.Reflection.AssemblyAlgorithmIdAttribute。默认使用SHA-1算法。
<对程序集进行签名>
对程序集进行签名

Microsoft对公钥进行哈希处理,并获取哈希值得最后8个字节

3.全局程序集缓存

由多个应用程序访问的程序集必须放到公认的目录,而且CLR在检测到对该程序集的引用时,必须知道检查该目录。这个公认位置就是全局程序集缓存(Global Assembly Cache,GAC)。GAC目录是结构化的:其中包含许多子目录,子目录名称用算法生成。永远不要将程序集文件手动复制到GAC目录;相反要用工具完成这项任务。

<GACUtil.exe>
GACUtil.exe
GACUtil.exe的i/开关方便开发人员在测试时使用。但如果是在生产环境中部署,建议安装或卸载程序集时除了指定/i或/u开关,还要指定/r开关。/r开关将程序集与Windows的安装与卸载引擎集成。简单的说,它告诉系统哪个应用程序需要程序集,并将应用程序与程序集绑定。

为什么要在GAC中"注册"程序集?假定两家公司都生成了名为OurLibrary的程序集,两个程序集都由一个OurLibrary.dll文件构成。这两个文件显然不能存储到同一个目录,否则最后一个安装的会覆盖第一个,造成应用程序被破坏。相反,将程序集安装到GAC,就会在%SystemRoot%\Microsoft.Net\Assembly目录下创建专门的子目录,程序集文件会复制到其中一个子目录。

4.在生成的程序集中引用强命名程序集

如果指定不含路径的文件名,CSC.exe会尝试在以下目录查找程序集(按所列顺序)

  1. 工作目录
  2. CSC.exe所在的目录,目录中还包含CLR的各种DLL文件
  3. 使用/lib编译器开关指定的任何目录
  4. 使用LIB环境变量指定到的任何目录

安装.Net Framework时,实际会安装Microsoft的程序集文件的两套拷贝,一套安装到编译器/CLR目录,一套安装到GAC的子目录。

5.强命名程序集能防篡改

用私钥对程序集进行签名,并将公钥和签名嵌入程序集,CLR就可验证程序集未被修改或破坏。每次应用程序执行并加载程序集时,都会对文件进行哈希处理,以牺牲性能为代价,保证程序集文件内容没有被篡改。CLR在运行时检测到不匹配的哈希值会抛出System.IO.FileLoadException异常。

将强命名程序集安装到GAC时,系统会执行检查,确保包含清单的文件没有被篡改。这个检查仅在安装时检查一次。除此之外,为了增强性能,如果强命名程序被完全信任,并加载到完全信任的AppDomain中,CLR将不检查该程序集是否被篡改。相反,从非GAC的目录加载强命名程序集时,CLR会校验程序集的清单文件,确保文件内容未被篡改,造成该文件每次加载都造成额外的性能开销。

6.延迟签名

.Net Framework提供了对延迟签名的支持(delayed signing),该技术也被称为部分签名(partial signing)。延迟签名允许只用公司的公钥生成程序集,暂时不用私钥。只有在开发阶段才延迟签名,打包和部署程序集肯定会签名。
如果是C#编译器,就指定/delaysign编译器开关;如果是Visual Studio就打开项目属性页,在"签名"选项卡中勾选"仅延迟签名";如果使用AL.exe,就指定/delay[sign]命令行开关。
目前生成的程序集没有有效签名。安装到GAC会失败,因为尚未对文件内容执行哈希处理——文件表面已被篡改。在需要安装到GAC的每台机器上,都必须禁止系统验证程序集文件的完整性。这要求使用SN.exe实用程序并指定-Vr命令行开关。用这个开关执行SN.exe,程序集的任何文件在运行时加载时,CLR都会跳过对其哈希值的检查。在内部,SN的-Vr开关会将程序集的身份添加到以下注册表子项中:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\StrongName\Verification

使用延迟签名技术开发程序集的步骤

  1. 开发期间,获取只含公司公钥的文件,/keyfile和/delaysign编译器开关编译程序集
csc /keyfile:MyCompany.PublicKey /delaysign MyAssembly.cs
  1. 生成程序集后执行以下命令,使CLR暂时信任程序集的内容,不对它进行哈希处理,也不对哈希值进行比较。这使程序集顺利安装到GAC(如果有必要的话)。现在,可以生成引用了这个程序集的其他程序集,并可随意测试程序集。注意,在每台开发用的机器上,一下命令行都只需执行一次,不必每次生成程序集都重复这一步:
SN.exe -Vr MyAssembly.dll
  1. 准备好打包和部署程序集时,获取公司的私钥并执行以下命令。如果愿意,可以将这个新版本安装到GAC中,但只有先完成步骤4才能把它安装到GAC中
SN.exe -Ra MyAssembly.dll MyCompany.PrivateKey
  1. 为了在实际环境中测试,请执行以下命令行,重新启用对这个程序集的验证:
SN -Vu MyAssembly.dll

为了确保密钥的安全性,密钥值绝对不能固定存储在磁盘文件中。“加密服务提供程序”(Cryptographic Service Provider,CSP)提供了对这些密钥的位置进行抽象的容器。如果公钥/私钥对在SCP容器中,必须为CSC.exe,AL.exe和SN.exe程序指定不同的开关。编译时(CSC.exe)指定/keycontainer开关而不是/keyfile开关;链接时(AL.exe)指定/keyname开关而不是/keyfile开关;使用强名称程序(SN.exe)对延迟签名的程序集进行重新签名时,指定-Rc而不是-R开关。

7.私有部署强名称程序集

事实上,只有由多个应用程序共享的程序集才应部署到GAC,不用共享的应该私有部署,私有部署达成了"简单复制部署"目标,而且能更好的隔离应用程序及其程序集。

8."运行时"如何解析类型引用

第二章开头展示了一下代码

public sealed class Program
{
	public static void Main()
	{
		System.Console.WriteLine("Hi!");
	}
}

编译这些代码并生成程序集(假定名为Program.exe)。运行应用程序,CLR会加载并初始化自身,读取程序集的CLR头,查找标识了应用程序入口方法(Main)的MethoDefToken,检索MethodDef元数据表找到IL代码在文件中的偏移量,将IL代码JIT编译成本机代码(编译时会对代码进行验证以确保类型安全),最后执行本机代码。
<Main方法>
Main方法
对这些代码进行JIT编译,CLR会检测所以类型和成员引用,加载它们的定义程序集(如果尚未加载)
解析引用的类型时,CLR可能在以下三个地方找的类型

  • 相同文件
    编译时便能发现对相同文件中的类型的访问,这称为早期绑定(early binding)。类型直接从文件中加载,执行继续
  • 不同文件,相同程序集
    "运行时"确保被引用的文件在当前程序集元数据的FileDef表中,检查加载程序集清单文件的目录,加载被引用的文件,检查哈希值以确保文件完整性。发现类型的成员,执行继续。
  • 不同文件,不同程序集
    如果引用的类型在其他程序集的文件中,"运行时"会加载被引用程序集的清单文件。如果需要的类型不在该文件中,就继续加载包含了类型的文件。发现类型的成员,执行继续。

下图演示类型绑定过程
<CLR怎样通过元数据来定位定义了类型的程序集文件>
类型绑定过程
对于CLR,所以程序集都根据名称,版本,语言文化和公钥来识别,但GAC根据名称,版本,语言文化,公钥和CPU架构来识别。在GAC中搜索程序集时,CLR首先搜索程序集的CPU架构专用版本,如果没有找到符合要求的就搜索不区分CPU的版本。

9.高级管理控制(配置)

以下是一个示例XML配置文件

<?xml version="1.0"?>
<configuration>
	<runtime>
		<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
			<probing privatePath="AuxFiles;bin\subdir" />
			<dependentAssembly>
				<assemblyIdentity name="SomeClassLibrary" publicKeyToken="32ab4ba45e0a69a1" culture="neutral" />
				<bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" />
				<codeBase version="2.0.0.0" href="http://www.Wintellect.com/SomeClassLibrary.dll" />
			</dependentAssembly>
			<dependentAssembly>
				<assemblyIdentity name="TypeLib" publicKeyToken="1f2e74e897abbcfe" culture="neutral" />
				<bindingRedirect oldVersion="3.0.0.0-3.5.0.0" newVersion="4.0.0.0" />
				<publisherPolicy apply="no" />
			</dependentAssembly>
		</assemblyBinding >
	</runtime>
</configuration>

这个XML文件为CLR提供了丰富的信息。

  • probing元素
    查找弱命名程序集时,检查应用程序基目录下的AuxFiles和bin\subdir子目录。对于强命名程序集,CLR检查GAC或者由codeBase元素指定的URL。只有在未指定codeBase元素时,CLR才会在应用程序的私有路径中检查强命名程序集
  • 第一个 dependentAssembly,assemblyBinding 和bindingRedirect 元素
    查找控制着公钥标记32ab4ba45e0a69a1的组织发布的,语言文化为中性的SomeClassLibrary程序集的1.0.0.0版本时,改为定位同一个程序集的2.0.0.0版本。
  • codeBase元素
    查找由控制着公钥标记32ab4ba45e0a69a1的组织发布的,语言文化为中性的SomeClassLibrary程序集的2.0.0.0版本时,尝试在以下URL处发现它:www.Wintellect.com/SomeClassLibrary.dll,codeBase元素也用于弱命名程序集。
  • 第二个 dependentAssembly,assemblyBinding 和bindingRedirect 元素
    查找控制着公钥标记1f2e74e897abbcfe的组织发布的,语言文化为中性的TypeLib程序集的3.0.0.0到3.5.0.0版本时,改为定位同一个程序集的4.0.0.0版本。
  • publisherPolicy元素
    如果生成TypeLib程序集组织部署了发布者策略,CLR应忽略该文件

发布者需要一种方式创建策略信息,新程序安装到用户机器上时,会安装这种策略信息。假定你是程序集的发布者,刚刚修复了几个bug,创建了程序集的新版本。打包要发送给所有用户的新程序集时,应同时创建XML配置文件。下面是用于SomeClassLibrary.dll程序集的示例文件(名为SomeClassLibrary.config)

<configuration>
	<runtime>
		<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
			<dependentAssembly>
				<assemblyIdentity name="SomeClassLibrary" publicKeyToken="32ab4ba45e0a69a1" culture="neutral" />
				<bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0" />
				<codeBase version="2.0.0.0" href="http://www.Wintellect.com/SomeClassLibrary.dll" />
			</dependentAssembly>
		</assemblyBinding >
	</runtime>
</configuration>

发布者只能为自己创建的程序集设置策略,另外发布者策略配置文件只能使用列出的元素;例如probing或publisherPolicy元素是不能使用的。该配置文件告诉CLR一旦发现对SomeClassLibrary程序集的1.0.0.0版本的引用就自动加载2.0.0.0版本。现在,发布者就可以创建包含该发布者策配置文件的程序集,像下面这样运行AL.exe

AL.exe      /out:Policy.1.0. SomeClassLibrary.dll
			/version:1.0.0.0
			/keyfile:MyCompany.snk
			/linkresource:SomeClassLibrary.config
  • /out
    告诉AL.exe创建新的PE文件,本例是Policy.1.0.SomeClassLibrary.dll其中除了一个清单什么都没有。程序集名称很重要。名称第一部分(Policy)告诉CLR该程序集包含发布者策略信息。第二部分和第三部分(1.0)告诉CLR这个发布者策略程序集适用于major和minor版本为1.0的任何版本的SomeClassLibrary程序集。发布者策略只能和程序集的major和minor版本号关联;不能和build和revision号关联。名称第四部分(SomeClassLibrary)指出与发布者策略对应的程序集名称。名称第五部分(dll)是现在要生成的发布者策略程序集文件的扩展名
  • /version
    标识发布者策略程序集版本;这个版本号与SomeClassLibrary程序集本身没有任何关系。发布者策略本身也有一套版本机制。例如,发布者今天创建一个发布者策略,将SomeClassLibrary的版本1.0.0.0重定向到版本2.0.0.0.未来,发布者可能将SomeClassLibrary的版本1.0.0.0重定向到版本2.5.0.0。CLR根据/version开关指定的版本号来选择最新版本的发布者策略程序集
  • /keyfile
    告诉AL.exe使用发布者的"公钥/私钥对"对发布者策略程序集进行签名。这一对密钥还必须匹配所有版本的SomeClassLibrary程序集使用的密钥对,只有这样CLR才知道SomeClassLibrary程序集和发布者策略文件由同一个发布者创建
  • /linkresource
    告诉AL.exe将XML配置文件作为程序集的一个单独的文件。最后程序集由两个文件构成,两者必须随同新版本SomeClassLibrary程序集打包并部署到用户机器。不能使用AL.exe的/embedresource开关将XML配置文件嵌入程序集文件,从而获得一个单文件程序集,因为CLR要求将XML文件独立。

发布者策略必须安装到GAC。
假定发布者推出发布者策略程序集时,因为某种原因,新程序集引入的bug比它能修复的bug还要多,那么管理员可指示CLR忽略发布者策略程序集。这要求编辑应用程序的配置文件并添加以下publisherPolicy元素

<publisherPolicy apply="no" />

该元素可作为应用程序配置文件的< assemblyBinding>元素的子元素使用,使其应用于所以程序集,也可作为应用程序配置文件的< dependantAssembly>元素的子元素使用,使其应用了特定程序集。

如果新版本程序集不兼容某个老版本,就不应创建发布者策略程序集

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值