版本号
如图:
- AssemblyVersion程序集版本号:存储在AssemblyDef清单元数据表中,虽然在文件属性面板中不显示,但是这个版本号对于CLR来说很重要,绑定强明明程序集时会用到这个,它唯一地标识了程序集。当A程序集引用了B程序集时,会将B程序集的AssemblyVersion嵌入到自己的AssemblyDef清单中,这样当CLR加载B的时候,就能准确的知道是程序集B的哪个版本。
- AssemblyFileVersion文件版本号:即属性面板中的文件版本,存储在Win32版本资源中,这个号仅供参考,CLR既不会检查,也不关心这个版本号。
- AssemblyInformationVersion:属性面单中的产品版本,存储在Win32版本资源中,当Assembley.cs中未设置此项时,属性中的产品版本将于AssemblyFileVersion保持一致。也是仅供参考,CLR不检查不适用。没什么用。
版本号的格式:major主版本号.minor次版本号.build内部版本号.reversion修订号。
前两个版本号构成了公众对版本的理解。
如果公司每天都编译这个程序集,那build号每天都需要递增。如果一天内需要多次编译程序集,则保持同一个build一致的情况下,增加修订号。
程序集的查找
很多开发者在编写代码过程都有这么一个需求,喜欢把引用第三方的一些dll放到根目录单独的一个文件夹中,使看起来更有条例一点。
比如我们有个控制台程序ConsoleTest,引用一大堆第三方dll,想把这些dll都放到与ConsoleTest.exe同级的一个文件夹subLib1和subLib2里,大致结构可能如下:
- 根目录
- ConsoleTest.exe
- ConsoleTest.exe.config
- subLib1
- a.dll
- b.dll
- subLib2
- c.dll
- d.dll
此时运行程序的话,因为查找不到引用的dll,会报FileNotFoundException。所以需要在ConsoleTest.exe.config
文件中做如下配置(必须是在应用程序主程序集文件名的config文件中配置)。
<configuration>
<runtime>
<assemblyBinding xmlns:"urn:schemas-microsoft-com:asm.v1">
<probing privatePath="subLib1;subLib2" />
</assemblyBinding>
</runtime>
</configuration>
probing
就制定了要查找的字目录,privatePath只能是根目录下的相对目录,不能指向根目录意外的目录。
CLR查找程序集顺序
这里假如CLR要查找一个AsmName.dll的程序集,而且程序的根目录叫AppDir,subLib1和subLib2已经在privatePath中配置过。查找顺序如下:
- AppDir\AsmName.dll
- AppDir\AsmName\AsmName.dll(上一步没找到,就往AsmName文件夹内找AsmName.dll文件)
- AppDir\subLib1\AsmName.dll
- AppDir\subLib1\AsmName\AsmName.dll
- AppDir\subLib2\AsmName.dll
- AppDir\subLib2\AsmName\AsmName.dll
- 如果再以上各个目录中都没找到目标程序集,则会用exe后缀替换dll后缀继续查找
- AppDir\AsmName.exe
- AppDir\AsmName\AsmName.exe
- AppDir\subLib1\AsmName.exe
- AppDir\subLib1\AsmName\AsmName.exe
- AppDir\subLib2\AsmName.exe
- AppDir\subLib2\AsmName\AsmName.exe
以上查找是基于中性语言文化的查找,如果向AsmName.dll应用了"en-US"语言文化,那么就会这么查找
- AppDir\en-US\AsmName.dll
- AppDir\en-US\AsmName\AsmName.dll
- AppDir\en-US\subLib1\AsmName.dll
- AppDir\en-US\subLib1\AsmName\AsmName.dll
- AppDir\en-US\AsmName.exe
- AppDir\en-US\AsmName\AsmName.exe
- AppDir\en-US\subLib1\AsmName.exe
- AppDir\en-US\subLib1\AsmName\AsmName.exe
- 如果没en-US文件夹或者文件夹内找不到,则会继续找en文件夹
- AppDir\en\AsmName.dll
- AppDir\en\AsmName\AsmName.dll
- AppDir\en\subLib1\AsmName.dll
- AppDir\en\subLib1\AsmName\AsmName.dll
- AppDir\en\AsmName.exe
- AppDir\en\AsmName\AsmName.exe
- AppDir\en\subLib1\AsmName.exe
- AppDir\en\subLib1\AsmName\AsmName.exe
强命名程序集与弱命名程序集
二者的结构完全相同,都有PE头、CLR头、元数据、清单表、IL。不同的是强程序集使用发布者的公钥/私钥进行了签名,对程序集进行了唯一性的标识。
弱程序集只能私有部署,即部署到应用程序的根目录或者字目录。强程序集可以私有部署或全局部署,即也可以部署到一些公有位置,CLR默认会检查这些位置。
Dll hell问题
全局部署会引起dll hell(dll 地狱)问题:两个公司可能会生成同名的dll,然后这两个强程序集都复制到相同的公有位置,最后一个复制的就是“老大”,造成所有正在使用原程序集的软件无法工作。
所以只根据文件名来区分程序集是不够的,CLR必须支持对程序集进行唯一性标识的机制,这就是强命名程序集。强程序集根据以下四个特征进行唯一性标识:
- 文件名(不含扩展名)
- 版本号(AssemblyVersion)
- 语言文化
- 公钥标记(public key token):从公钥派生出的小哈希值
如下就是两个程序集:
ConsoleFrame, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b77a5csdfgsdfg3434dsqfg
ConsoleFrame, Version=1.0.0.0, Culture="en-US", PublicKeyToken=b77a5csdfgsdfg3434dsqfg
弱程序集的public key token为null
ConsoleFrame, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
对弱程序集签名,变成强程序集
右键项目-属性,选择Signing-Sign the assembly即可。
然后在另一个项目中,引用ConsoFrame程序集,可以看到,已经生成了PublicKeyToken。
在这里我们新建另一个程序集ConsoleFrame2,然还用同一个AA.pfx公钥进行签名,可以看到ConsoleFrame与ConsoleFram2的PublicKeyToken完全一致:
生成强程序集时,公钥也会被嵌到dll里。
全局程序集缓存GAC
之前有讲过强程序集可以放到一个公共的位置,供所有的程序访问,这个位置就是GAC(Global Assembly Cache)。这个位置不是固定的,不同的CLR版本可以有不同的位置,但一般在以下目录都能发现:
%SystemRoot%\Microsoft.NET\Assembly
GAC目录下有很多子目录,子目录的名称是用算法生成的,不要自己手动把dll复制到某个目录里去,没用。应该使用GACUtil.exe工具复制,一般电脑里都有这个,使用Everything工具全局搜即可。
至此,dll hell问题得到解决。
在你的项目中如何引用强程序集?
- 你知道要引用dll的路径,直接项目上右键-添加引用即可。
- 你不知道引用dll的路径,那编译器CSC.exe会从以下位置开始查找:
- 工作目录
- CSC.exe所在的目录,目录中包含CLR的各种dll文件
- 编译时使用/lib参数指定的目录
- 使用LIB环境变量指定的目录
注意:编译查找dll进行编译的位置可不是程序运行时加载dll的位置,这是两码事。
到这里你应该理解到了.NET为什么会有.NET SDK
和.NET Runtime
两种包可供下载安装了,如果只是运行.net程序而不进行开发那么只下载runtime就行。安装SDK时会安装同一程序集的两套拷贝,一份放到编译器的目录,另一份放到GAC里,而安装runtime只需要放到GAC就行。