《CLR via C# 》读书笔记(1) -- .Net程序是如何生成的
说到.NET基础,我觉得应该首先应该要知道一组概念,我觉得作为.NET程序员,这应该算是常识。有可能我目前的理解也不算正确,希望能在以后的积累中逐步完善。
CLR
- CLR (Common Language Runtime)它为.NET Application提供一个运行时的环境。毕竟.NET程序和普通的Windows应用程序(比如说用C++开发的程序)运行方式是不一样的。在
运行时,它有自己的内存管理机制,有自己的执行模型,有自己的异常处理,有自己的线程同步等。而这些都是由CLR提供。相当于一个容器。
CTS
- CTS (Common Type System) .Net是一个基于类型的平台,所有的事物都必须被一个类型所包含。没有像C++中的全局变量,全局函数这类东西。那么CTS是一系列用来规范
类型的规则。比如说:规定类型可以包含字段,方法,属性,事件,规定类型可以继承,规定类型的可访问类型可以有private, public, internal等。个人理解是一种语法规则。
CLS
- CLS (Common Language Specification) CLR其实提供了很多的功能,而每一种面向CLR的语言(比如说C#,VB.NET)可能不需要暴露CLR所有的功能给开发人员。但是如果每种语言所支持的
CLR功能不一样,相互之间的调用便会出现问题了(目前可以举出一个例子:CLR是支持throw 任何类型,VB.NET是完全支持CLR的,但是C#只支持throw Exception的派生类。如果在C#中调用VB.NET的
代码,那VB.NET中的有些throw肯定无法在C#中catch住。)。为了能够跨语言调用,必须有一个规范来统一各个语言所必须支持CLR中的功能集。这个规范便是CLS。他定义了CLR强大功能中的一个子集合,这是
所有面向CLR的语言必须要支持的。这样能才能实现跨语言的调用。
程序集
- 程序集(Assembly)我觉得用最通俗的语言讲就是一些类的集合。但是这样说应该是比较片面的,至少程序集里面还能嵌入资源文件。
官方说法应该是程序集是由一个或多个模块加上资源文件组成,每个模块由描述该模块所包含/引用类型的元数据和IL代码组成。
回到和标题相关的内容,平常我们编写一个.NET程序的过程应该是
1. 创建一个VS Project
2. 编写代码
3. 编译Project,接着VS便给我们产生了一个exe程序
4. 我们双击一下这个exe程序,我们绘制的界面就出现了。
作为.NET程序员,我觉得应该要了解得更多一点。这样才能知其所以然,呵呵。
- 剖析生成的exe/dll文件
1. 文件组成结构
编绎器生成的exe/dll的组成结构为:PE32(+) Header,CLR Header,程序集的元数据以及源代码经过编绎后生成的IL代码。具体表示内容见下表:
我们可以通过微软提供的工具dumpbin,来查看PE32(+) Header 和 CLR Header. 以下是我通过该工具获取的某dll的部分PE32(+) Header和CLR header
PE32 HeaderFile Type: DLL
FILE HEADER VALUES
14C machine (x86)
3 number of sections
50AA2CD2 time date stamp Mon Nov 19 20:57:54 2012
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
2102 characteristics
Executable
32 bit word machine
DLL
CLR Header
clr Header:
48 cb
2.05 runtime version
2098 [ 71C] RVA [size] of MetaData Directory
1 flags
IL Only
0 entry point token
0 [ 0] RVA [size] of Resources Directory
0 [ 0] RVA [size] of StrongNameSignature Directory
0 [ 0] RVA [size] of CodeManagerTable Directory
0 [ 0] RVA [size] of VTableFixups Directory
0 [ 0] RVA [size] of ExportAddressTableJumps Directory
0 [ 0] RVA [size] of ManagedNativeHeader Directory
可以通过ILDasm这个工具来查看程序集中的元数据,下表是通过该工具查看到的某dll的元数据。
-------------------------------------------------------
TypDefName: Entity.Person (02000002)
Flags : [Public] [AutoLayout] [Class] [AnsiClass] [BeforeFieldInit] (00100001)
Extends : 01000001 [TypeRef] System.Object
Field #1 (04000001)
-------------------------------------------------------
Field Name: <Name>k__BackingField (04000001)
Flags : [Private] (00000001)
CallCnvntn: [FIELD]
Field type: String
CustomAttribute #1 (0c000002)
-------------------------------------------------------
CustomAttribute Type: 0a000011
CustomAttributeName: System.Runtime.CompilerServices.CompilerGeneratedAttribute :: instance void .ctor()
Length: 4
Value : 01 00 00 00 > <
ctor args: ()
Field #2 (04000002)
-------------------------------------------------------
Field Name: <Age>k__BackingField (04000002)
Flags : [Private] (00000001)
CallCnvntn: [FIELD]
Field type: I4
CustomAttribute #1 (0c000012)
-------------------------------------------------------
CustomAttribute Type: 0a000011
CustomAttributeName: System.Runtime.CompilerServices.CompilerGeneratedAttribute :: instance void .ctor()
Length: 4
Value : 01 00 00 00 > <
ctor args: ()
Method #1 (06000001)
-------------------------------------------------------
MethodName: get_Name (06000001)
Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] (00000886)
RVA : 0x00002050
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: String
No arguments.
CustomAttribute #1 (0c000001)
-------------------------------------------------------
CustomAttribute Type: 0a000011
CustomAttributeName: System.Runtime.CompilerServices.CompilerGeneratedAttribute :: instance void .ctor()
Length: 4
Value : 01 00 00 00 > <
ctor args: ()
Method #2 (06000002)
-------------------------------------------------------
MethodName: set_Name (06000002)
Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] (00000886)
RVA : 0x00002067
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
1 Arguments
Argument #1: String
1 Parameters
(1) ParamToken : (08000001) Name : value flags: [none] (00000000)
CustomAttribute #1 (0c000011)
-------------------------------------------------------
CustomAttribute Type: 0a000011
CustomAttributeName: System.Runtime.CompilerServices.CompilerGeneratedAttribute :: instance void .ctor()
Length: 4
Value : 01 00 00 00 > <
ctor args: ()
Method #3 (06000003)
-------------------------------------------------------
MethodName: get_Age (06000003)
Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] (00000886)
RVA : 0x00002070
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: I4
No arguments.
CustomAttribute #1 (0c000013)
-------------------------------------------------------
CustomAttribute Type: 0a000011
CustomAttributeName: System.Runtime.CompilerServices.CompilerGeneratedAttribute :: instance void .ctor()
Length: 4
Value : 01 00 00 00 > <
ctor args: ()
Method #4 (06000004)
-------------------------------------------------------
MethodName: set_Age (06000004)
Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] (00000886)
RVA : 0x00002087
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
1 Arguments
Argument #1: I4
1 Parameters
(1) ParamToken : (08000002) Name : value flags: [none] (00000000)
CustomAttribute #1 (0c000014)
-------------------------------------------------------
CustomAttribute Type: 0a000011
CustomAttributeName: System.Runtime.CompilerServices.CompilerGeneratedAttribute :: instance void .ctor()
Length: 4
Value : 01 00 00 00 > <
ctor args: ()
Method #5 (06000005)
-------------------------------------------------------
MethodName: PrintName (06000005)
Flags : [Public] [HideBySig] [ReuseSlot] (00000086)
RVA : 0x00002090
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
Method #6 (06000006)
-------------------------------------------------------
MethodName: .ctor (06000006)
Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886)
RVA : 0x0000209f
ImplFlags : [IL] [Managed] (00000000)
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
Property #1 (17000001)
-------------------------------------------------------
Prop.Name : Name (17000001)
Flags : [none] (00000000)
CallCnvntn: [PROPERTY]
hasThis
ReturnType: String
No arguments.
DefltValue:
Setter : (06000002) set_Name
Getter : (06000001) get_Name
0 Others
Property #2 (17000002)
-------------------------------------------------------
Prop.Name : Age (17000002)
Flags : [none] (00000000)
CallCnvntn: [PROPERTY]
hasThis
ReturnType: I4
No arguments.
DefltValue:
Setter : (06000004) set_Age
Getter : (06000003) get_Age
0 Others
TypeRef #1 (01000001)
-------------------------------------------------------
Token: 0x01000001
ResolutionScope: 0x23000001
TypeRefName: System.Object
MemberRef #1 (0a000013)
-------------------------------------------------------
Member: (0a000013) .ctor:
CallCnvntn: [DEFAULT]
hasThis
ReturnType: Void
No arguments.
.....
TypeRef #20 (01000014)
-------------------------------------------------------
Token: 0x01000014
ResolutionScope: 0x23000001
TypeRefName: System.Console
MemberRef #1 (0a000012)
-------------------------------------------------------
Member: (0a000012) WriteLine:
CallCnvntn: [DEFAULT]
ReturnType: Void
1 Arguments
Argument #1: String
Assembly
-------------------------------------------------------
Token: 0x20000001
Name : Entity
Public Key :
Hash Algorithm : 0x00008004
Version: 1.0.0.0
Major Version: 0x00000001
Minor Version: 0x00000000
Build Number: 0x00000000
Revision Number: 0x00000000
Locale: <null>
Flags : [none] (00000000)
AssemblyRef #1 (23000001)
-------------------------------------------------------
Token: 0x23000001
Public Key or Token: b7 7a 5c 56 19 34 e0 89
Name: mscorlib
Version: 4.0.0.0
Major Version: 0x00000004
Minor Version: 0x00000000
Build Number: 0x00000000
Revision Number: 0x00000000
Locale: <null>
HashValue Blob:
Flags: [none] (00000000)
2. 元数据
所谓元数据即用来描述数据的数据,具体到程序集的元数据则是用来描述程序集的数据。其实这些元数据便是.NET反射机制的数据来源。上面通过ILDasm获得的是结构化处理过之后的结果。而真实存储于文件中的是一张张二维的元数据表,下表是常用的元数据定义表和引用元数据表:
这些都是在编绎的时候,编绎器根据源代码进行分析,结合编绎源码时传递的对外部程序集的引用命令,然后总结成上面所描述的元数据表。
- 多文件程序集
其实在几年的工作过程中,我还没有碰到过多文件程序集。在程序集的定义中,其实是有一个模块(Module)的概念。一个程序集可以由多个模块组成。
每一个模块是指一个.netmodule的文件。(貌似通过VS无法生成这种类型的文件,必须要通过手动调用CSC编绎器程序去生成)当然如果程序集只有一个模块,那么这个模块当然也就直接在dll/exe文件中了。每一个模块都会有上面所描述文件结构,也会包含元数据。但是CLR不能加载这种模块文件,CLR加载的最小单元是程序集。模块文件必须被关联到某个程序集中,并且有一个dll/exe文件与这个程序集相对应,该模块文件才能被CLR使用。而这个dll/exe文件充当了程序集所有模块的关联者。如下图:
这样,CLR可以在真正需要的时候才去加载多模块程序集中的某个特定的模块。可以避免加载一载根本不需要用到的模块。
那到这里,出现了一个问题,在dll/exe文件中没有用来保存dll/exe与模块文件之前的关系的元素。CLR如何知道该程序集包含哪几个模块呢?因此另一种类型
的元数据表便诞生了---清单元数据表。 见下表:
通过清单元数据,编译器将程序集的组成结构保存了下来。 - 强命名程序集
.NET提供的这个功能是我一直觉得很神秘的东西,原因是因为一直都没有机会用过。(人有时候很奇怪,总是会觉得没有接触过的东西很神秘,
即使它其实只是个很普通的玩意。)
通过强名,CLR通过 AssemblyName,Version,Culture,PublicKey 四个元素来标识一个程序集。从而增强了程序集的可区分性。
我觉得强名程序集主要解决以下两方面问题:
1. 通过采用一种比较强的机制标识程序集,从而解决安装程序集到GAC时无法区分不分提供者的同名程序集以及同一程序集的不同版本等问题。(如果是私有部署,问题还是无法解决,因为相同文件名的文件会被替换)
2. 采用公钥/私钥对进程序集进行加密,然后加载时进行验证从而保证程序集的有效性。
1. 生成
讲了半天,还是先来看看如何生成一个强名的程序集。
主要步骤是:
1. 先利用.NET提供的工具 SN.exe产生一个私钥/公钥对文件。
2. 在编译程序集是提供给编译器,让它根据1中产生的私钥/公钥对文件对程序集进行加密与签名。
通过这样编译出来的程序集便是用私钥签过名的,并且编译器会将公钥保存在程序集的清单元数据中。
签名处理的方式是对PE文件中除了强名数据外的其他信息进行Hash,然后将Hash值用私钥进行加密后存放于CLR的Header中。如下图:
通过签名,当CLR加载该程序集时可以通过对加载的程序集进行Hash验证。则可以知道该程序集是否被篡改。
让我们用工具来一探虚实:
1. 通过dumpbin来查看CLR Header
编译出来的程序集文件的CLR Header里显示了该程序集是Strong Name Signed.
再来用ILDasm看看清单元数据:View Code// Metadata version: v4.0.30319
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 4:0:0:0
}
.assembly Entity
{
.custom instance void [mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 06 45 6E 74 69 74 79 00 00 ) // ...Entity..
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.Versioning.TargetFrameworkAttribute::.ctor(string) = ( 01 00 1A 2E 4E 45 54 46 72 61 6D 65 77 6F 72 6B // ....NETFramework
2C 56 65 72 73 69 6F 6E 3D 76 34 2E 30 01 00 54 // ,Version=v4.0..T
0E 14 46 72 61 6D 65 77 6F 72 6B 44 69 73 70 6C // ..FrameworkDispl
61 79 4E 61 6D 65 10 2E 4E 45 54 20 46 72 61 6D // ayName..NET Fram
65 77 6F 72 6B 20 34 ) // ework 4
.custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 06 45 6E 74 69 74 79 00 00 ) // ...Entity..
.custom instance void [mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.
.custom instance void [mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 12 43 6F 70 79 72 69 67 68 74 20 C2 A9 20 // ...Copyright ..
20 32 30 31 32 00 00 ) // 2012..
.custom instance void [mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.InteropServices.ComVisibleAttribute::.ctor(bool) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = ( 01 00 24 31 38 37 31 33 65 65 31 2D 63 61 62 62 // ..$18713ee1-cabb
2D 34 39 31 61 2D 39 34 35 63 2D 36 31 63 30 63 // -491a-945c-61c0c
32 62 36 61 39 36 64 00 00 ) // 2b6a96d..
.custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 ) // ...1.0.0.0..
// --- The following custom attribute is added automatically, do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 )
.publickey = (00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00 // .$..............
00 24 00 00 52 53 41 31 00 04 00 00 01 00 01 00 // .$..RSA1........
A1 C0 32 95 08 03 92 3F 1B 1D BA 20 B0 B4 1B F5 // ..2....?... ....
9C 3A E5 C5 88 50 74 7B 66 4D A9 A6 4C CB D5 30 // .:...Pt{fM..L..0
80 5B FB 98 B3 4B 55 CE D6 BC 53 BF 80 A7 18 39 // .[...KU...S....9
DF 9E 3C AD 5F 34 B8 B0 6F AF 87 CD 9B 36 66 F3 // ..<._4..o....6f.
27 FF 7E E9 4E 52 5D 17 61 49 F6 89 54 2D B0 AF // '.~.NR].aI..T-..
FD 7E EA 75 D5 A7 47 08 10 FD 18 91 99 4E 99 DA // .~.u..G......N..
78 0A DE BE 1F D7 00 21 A5 A5 99 0B 89 53 43 8E // x......!.....SC.
AB 78 5D F9 A1 C6 16 23 4E C6 65 3D F5 8F D8 DC ) // .x]....#N.e=....
.hash algorithm 0x00008004
.ver 1:0:0:0
}
.module Entity.dll
// MVID: {72590280-AC77-4C2F-BA62-BF18ABD776B2}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000009 // ILONLY
// Image base: 0x03760000
可以看到清单元数据中包含了一个PublicKey元素(即公钥)。
2. 使用
当加载一个强名的程序集时,必须提供强名标识符
Assembly entityAssembly = Assembly.Load(assemblyName);
此处的PublicKeyToken是公钥的64位Hash值。可以使用SN工具来获取私钥/公钥对文件中的公钥以及公钥标记。
此处如果版本不对:
Assembly entityAssembly = Assembly.Load(assemblyName);
或公钥标记不对:
Assembly entityAssembly = Assembly.Load(assemblyName);
都会导致该程序集加载失败:
终于写完了这一节,感觉写点东西比看东西费劲很多啊。不过在写的过程中还是加深了自己的理解。勉励一下自己,继续坚持。