C#与CLR学习笔记(2)—— 程序集的部署、查找与强名称

本文是《CLR via C#》一书第2章和第3章的要点总结。

上篇内容:《CLR via C#》读书笔记(1)—— 程序集的结构与CLR的启动

目录

程序集的生成与部署

程序集的生成

响应文件

语言文化

应用程序的管理配置与文件探测

程序集文件探测顺序

强命名程序集

强名称

公钥与私钥

创建强名称程序集

签名的过程

CLR对强命名程序集的检查


程序集的生成与部署

程序集的生成

使用 csc.exe /out:outname  /t:target  /r:referenceFiles 命令可编译一个源文件。其中/t指定生成的程序集类型,/r指定引用的其他文件。例如,若我们的代码中用到了 System.Console 类型,由于它是微软实现好的类型,因此需要指定 /r:MSCorLib.dll 引入该外部类型。然而实际中,MSCorLib.dll是个特殊的程序集,即使我们不指定 /r:MSCorLib.dll,它也会被编译器自动引用,因为它包含了所有核心类型例如 Sreing, Int32 等等。

响应文件

相应文件(.rsp)时包含了一组编译器命令行开关的本文文件,执行 csc.exe 时,编译器会自动打开相应文件,并读取、执行其中的开关。安装.net framework 时,会在 %SystemRoot%\Microsoft.NET\Framework(64)\vX.X.X 目录中默认安装全局 CSC.rsp 响应文件。此文件中引用的dll 不必在编译时再指定 /r 开关引用。

语言文化

如果应用程序包含语言文化特有的资源,微软建议专门创建一个特有资源文件,它不包含任何代码,作为主应用程序的一个附属程序集(satellite assembly)。部署只含有资源数据的附属程序集时,应该把它放到专门的子目录中,子目录名称与语言文化的文本匹配,例如,如果应用程序的根目录时 D:\MyApp,与中文对应的附属程序集放大 D:\MyApp\zh-cn 子目录中。在运行时,使用 System.Resources.ResourceManager 类访问附属程序集的资源。

应用程序的管理配置与文件探测

为了实现对应用程序的管理控制,可在应用程序目录中放入配置文件。CLR会解析文件内容来更改有一次文件的定位和加载策略。

配置文件包含XML代码,它既能和应用程序关联,也可以与机器关联。

例如,如果如果根目录下的program.exe要引用一个不在应用程序根目录的程序集,CLR会抛出 System.IO.FileNotFound 异常,为了解决此问题,可为应用程序主程序集创建一个XML配置文件,且此文件必须与主程序集名称相同,以.config为扩展名,即program.exe.config。

例如,要想实现如下部署:

可在program.exe.config 中添加:

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas­microsoft­com:asm.v1">
      <probing privatePath="AuxFiles" />
    </assemblyBinding>
  </runtime>
</configuration>

CLR尝试定位程序集文件时,总是先在应用程序根目录查找,然后就会查找 AuxFiles 子目录。可为 probing 元素的 privatePath 特性指定多个以分号隔开的路径,这里的路径都必须时相对根目录的相对路径。

对于一个可执行应用程序(.exe),配置文件必须在应用程序的根目录,且必须采用exe文件全名作为文件名,以.config为扩展名。

对于 ASP.NET 应用程序,文件必须在Web 应用程序的虚拟根目录中,且命名必须为 Web.config。子目录也可以有 Web.config,且会继承父级配置项。例如,位于 http://Wintellect.com/Training 的Web 程序既会使用虚拟根目录的 web.config 配置,也会使用 Training 目录下的配置。

 在安装.Net 时,会自动创建一个 Machine.config 全局配置文件,位于 %SystemRoot%\Microsoft.NET\Framework\version\CONFIG 路径下。每个CLR版本都会对应一个 Machine.config 文件。一般情况下不要修改该配置文件,因为会影响机器上其他应用程序。

程序集文件探测顺序

CLR 通过元数据加载 IL 引用的类型或方法

如上图所示,CLR 在运行时会通过元数据查找并定位 IL 所引用的类型或方法。如果引用的类型在另一个程序集上,那么CLR会通过 AssemblyRef 查找程序集文件。由于 AssemblyRef 记录表中只包含文件名、版本、文化、公钥标记,不包含扩展名,因此 CLR会自动加上扩展名(.dll/.exe)查找程序集文件。

CLR定位程序集时会扫描几个子目录,顺序如下:(其中,firstPrivatePath 和 secondPrivatePath 时通过上述配置文件指定的)

AppDir\AsmName.dll
AppDir\AsmName\AsmName.dll
AppDir\firstPrivatePath\AsmName.dll
AppDir\firstPrivatePath\AsmName\AsmName.dll
AppDir\secondPrivatePath\AsmName.dll
AppDir\secondPrivatePath\AsmName\AsmName.dll
...

因此,如果程序集位于其同名子目录下,是不需要配置的,因为CLR会默认扫描该子目录。

如果上述任何子目录都找不到目标程序集,CLR会从头再来,用.exe 扩展名替换.dll ,再找不到就抛出 FileNotFoundException 异常。

附属程序集遵循类似的规则,知识CLR会在与语言文化同名的子目录下查找,例如:

C:\AppDir\en-­US\AsmName.dll
C:\AppDir\en­-US\AsmName\AsmName.dll
C:\AppDir\firstPrivatePath\en-­US\AsmName.dll
C:\AppDir\firstPrivatePath\en­-US\AsmName\AsmName.dll
C:\AppDir\secondPrivatePath\en-­US\AsmName.dll
C:\AppDir\secondPrivatePath\en-­US\AsmName\AsmName.dll

C:\AppDir\en­-US\AsmName.exe
C:\AppDir\en­-US\AsmName\AsmName.exe
C:\AppDir\firstPrivatePath\en-­US\AsmName.exe
C:\AppDir\firstPrivatePath\en-­US\AsmName\AsmName.exe
C:\AppDir\secondPrivatePath\en-­US\AsmName.exe
C:\AppDir\secondPrivatePath\en-­US\AsmName\AsmName.exe

C:\AppDir\en\AsmName.dll
C:\AppDir\en\AsmName\AsmName.dll
C:\AppDir\firstPrivatePath\en\AsmName.dll
C:\AppDir\firstPrivatePath\en\AsmName\AsmName.dll
C:\AppDir\secondPrivatePath\en\AsmName.dll
C:\AppDir\secondPrivatePath\en\AsmName\AsmName.dll

C:\AppDir\en\AsmName.exe
C:\AppDir\en\AsmName\AsmName.exe
C:\AppDir\firstPrivatePath\en\AsmName.exe
C:\AppDir\firstPrivatePath\en\AsmName\AsmName.exe
C:\AppDir\secondPrivatePath\en\AsmName.exe
C:\AppDir\secondPrivatePath\en\AsmName\AsmName.exe

 

强命名程序集

强名称

私有部署的程序集(部署到应用程序根目录或子目录中的程序集)通常来说没有太大问题,但是全局部署的程序集(共享程序集)必须放在公共的目录下(例如System32目录),这就会导致一系列问题,例如 DLL Hell 问题。

为了避免 DLL Hell 问题,只根据文件名来区分程序集是不够的,因此便诞生了强命名程序集(strongly named assembly)。强命名程序集与一般程序集结构完全相同,生成工具也相同。区别在于,强命名程序集使用了发布者的 公钥/私钥对 进行了签名。

弱命名的程序集只能以私有的方式部署,而共享程序集必须要有强名称。

强名称的四大特性:

  • 文件名(不包括扩展名)
  • 版本号
  • Culture
  • 公钥标记(public key token),一般公钥很大,为了节约空间,经常使用公钥哈希值的最后8个字节来代表公钥,这个哈希值就是公钥标记。

以下程序集标识字符串(即 程序集显示名称)标识了4个不同的程序及文件:

"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"

公钥与私钥

可利用VS自带的SN.exe程序生成公钥/私钥对。例如:

SN -k MyCompany.snk

上述可以创建一个包含了公钥和私钥的snk文件。再使用

 sn -p MyKeys.snk MyPublicKey.publickey 

命令可将公钥提取到 MyPublicKey.publickey 文件中,然后使用 

sn -tp MyPublicKey.publickey 

命令可查看公钥以及公钥标记,如下图。其中给出的sha1算法是默认的生成公钥标记的算法,也可在提取公钥时手动指定其他哈希算法。

创建强名称程序集

可通过编译器创建一个强名称程序集:

csc /keyfile:MyKeys.snk Program.cs

使用上述命令,编译时,编译器用私钥对程序集进行签名,并将公钥签入清单中。所以,只能对含有清单的程序集进行签名

另外,通过VS也能创建强名称程序集。选中项目-【属性】-【签名】- 勾选【为程序集签名】,在选择密钥文件下拉框中选择密钥文件,或者选择【新建】来生成一个密钥文件,就可以生成强名称程序集。

签名的过程

首先,程序集清单的 FileDef 表中包含了每个组成文件的名称,每加入一个文件,都会对该文件内容进行一次哈希运算(默认使用SHA1),哈希值与文件名一同存放在 FileDef 表中。当包含清单的PE文件创建以后,PE文件的全部内容(除了 Authenticode signature,程序集强名称数据,PE头校验和)做一次哈希运算,这个哈希值使用发布者的私钥进行一次签名(加密),得到的RSA数字签名被存放在PE文件中的一个保留区域中。然后,PE文件的CLR头会被更新,来说明数字签名在文件中嵌入的位置。

发布者的公钥也会被嵌入到PE文件的 AssemblyDef 清单元数据表。这样,一个强命名程序集就完成了。

强名称程序集的签名过程

注意,为了节约空间,在 AssemblyRef 元数据中存放引用的其他程序集的公钥标记,而 AssemblyDef 中存放自己完整的公钥,以防止文件被篡改。从程序集的 AssemblyDef 清单中抽取公钥标记、程序集名称、Culture、版本信息,就可以得到程序集标识字符串( 程序集显示名称)。

CLR对强命名程序集的检查

在将一个强命名程序集安装到GAC中时,系统会自动校验该程序集,以防止不安全的程序集进入GAC中。系统对包含清单的那个主模块文件内容进行哈希处理,与经过公钥解密后的签名进行比较,只有二者一致时,程序集才能被安装到GAC中。

在运行时,若需要加载一个强命名的程序集,CLR首先从GAC中查找该程序集。若该程序集位于GAC中,且被加载到一个信任的AppDomain中,CLR将不会再检查是否被篡改。若从非GAC目录加载强命名程序集,CLR会对程序集进行检查,这会牺牲一部分性能,来确保安全性。如果校验未通过,会抛出 System.IO.FileLoadException 异常。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值