【读书笔记】Microsoft.Net框架程序设计, Part I: Basics of the Microsoft .NET Framework(中文) (转)
English Version go here.
By Alva Chien
Part I: Basics of the Microsoft .NET Framework
Chapter 1: The Architecture of the .NET framework Development Platform
1. .Net中的common language runtime (CLR) 是一个可以被多个不同语言使用的runtime. 不管使用哪种编译器,所生成的结果都是一个managed module. 一个managed module是一个标准的Windows Portable executable (PE) 文件,这个文件必须由CLR来执行.
一个managed module的组成:
Part |
Description |
PE Header |
标准的 Windows PE header, 类似于Common Object File Format (COFF) header. 这个header包含了文件类型: GUI, CUI, or DLL, 它同样包含了一个文件创建时的时间戳. 对只包含IL代码的的modules,PE header中的大部分信息被忽略. 对包含native CPU代码的module,这个header包含了关于native CPU代码的信息. |
CLR Header |
包含了一个managed module所必须的信息(由CLR和其工具解析) . 这个header包含了所需的CLR版本和一些状态标志位, managed module的入口方法(Main方法,一个MethodDef metadata token), metadata的位置/大小, 资源, strong name, 一些标志位和其他信息. |
Metadata |
每个managed module都包含metadata表. 这些表有两种类型: 定义types和members的的表和定义Referenced的type和members的表. |
Intermediate language (IL) code |
由编译器编译的代码,真正执行时候CLR会将IL代码编译为native CPU instructions. |
2. 一个 assembly是一个或多个managed modules/资源文件的逻辑组合, 它是最小的可重用,拥有版本信息和安全信息的单元. PE文件包含一块叫做manifest的数据块. 一个manifest是另一个metadata tables的集合. 这些tables定义了组成一个assembly的文件, 由这些文件定义的exported types, 跟这个assembly关联的资源或数据文件. 一个典型的例子: 把一些较少使用的types或资源定义在assembly的一个独立文件中,这个文件只会在其中的type或资源被使用的时候才被加载.
3. 可以通过查找MSCorEE.dll 文件来判断.NET是否被安装,这个文件位于%windir%\system32 文件夹中. 但是,一台机器允许同时安装几个版本的.Net Framewor.可以通过查看一下注册表的键值来判断当前.NET的版本: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\policy
4. 如何装载 CLR:
- 当创建一个EXE assembly, 编译器/链接器在生成的assembly’s PE file header嵌入一些特别的信息在.text section.
- 当创建一个DLL assembly, 如果一个unmanaged程序调用LoadLibrary 装载它, 这个DLL’s 入口函数知道如果装载CLR来正确处理assembly中的代码.
5. 当创建一个EXE assembly, 一个的6字节的x86 sub function被嵌入到.text section: JMP _CoreExeMain. 这个_CoreExeMain函数从MSCorEE.dll导入, 这个MSCoreEE.dll (Microsoft Component Object Runtime Execution Engine) 在assembly file’s .idata section定义了引用. 所以当assembly像正常程序一样启动时, MSCoreEE.dll被导入到这个进程的地址空间, 这时_CorExemain 函数的地址被获取,同时JMP instruction被执行. 这个函数将初始化CLR并且查找这个可执行assembly’s CLR header的可执行入口方法,这个方法的IL代码将被编译为native CPU instructions, 随后CLR跳转这个native code, 这时,程序已经启动.
6. 当创建一个DLL assembly, 一个类似的6字节长的x86 stub function被嵌入到.text section: JMP _CorDllMain. _CorDllMain 函数同样从MSCorEE.dll中引入, 这个DLL’s .idata section 中包含MSCorEE.dll的引用定义. 所以, 当LoadLibrary 执行时, _CorDllMain 被调用来初始化CLR并返回给应用程序来继续执行.
7. 这个6字节的stub function仅仅在非Windows XP系统中被添加. 在Windows XP以及以后版本中, OS loader检查嵌入managed code的文件的PE file header的directory entry 14. (IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR定义在WinNT.h) 如果这个directory entry存在并且拥有一个非0值, OS loader忽略这个文件的导入 (.idata) section并且自动装载MSCorEE.dll到进程的地址空间并直接跳转到对应的函数.
8. 最后一个关于managed PE文件的注意点: 他们总是使用32位PE文件格式, 而不是64位PE文件格式. 在64位系统上, OS loader检查这个managed 32位PE文件并知道如何去创建一个64位地址空间.
9. 至于知识产权保护, 对于所有的编译位IL的代码并且IL代码能被很容易被Disassembler程序reverse, 可以使用一个第三方提供的obfuscator工具. 这些工具“scramble” managed module’s metadata中所有private symbols的名称. 另外,可以把一些代码编译为unmanaged DLL并在assembly中调用它.
10. 所有的高级语言都只是提供了CLR的一个功能子集. 但是,IL提供了CLR所有的功能.
11. 如果Assembly中的一个方法第一次被调用,它的IL代码需要被编译为native CPU代码, 这部分功能由CLR’s JIT (just-in-time)完成. JIT编译器在内存中保存了native CPU instructions, 所以当应用程序终止时候,编译的代码都将失效. 另外一个值得记住的重点是JIT编译过程当中有一个专门的优化编译代码的过程.
12. Microsoft .Net framework提供了一个名为NGen.exe的工具; 这个工具编译一个assembly的全部IL代码为native CPU代码并将结果保存到一个硬盘文件.
13. IL是一个stack-based的语言, 所有的IL instructions会把operands压栈,并将结果弹出. IL没有提供操作CPU寄存器的指令.
14. 当编译IL为native CPU指令时,CLR执行一个verification的过程. Verification检查IL代码来保证代码是 “safe”. 如果IL代码被监测为 “unsafe”, 一个名为System.Security.VerifierException的异常被抛出. 默认情况下, C#编译器编译出 “safe”的代码. 可以使用PEVerify工具(PEVerify.exe)来检查assembly中IL代码是否安全. 值得注意的是administrator可以选择是否禁用verification 过程(通过.Net Framework Configuration administrative 工具).
15. 当对managed代码的verification过程完成之后,多个managed的应用程序可以运行在同一个Windows虚拟地址空间. 因为Windows进程需要需要操作系统的资源, 所以, 通过在一个进程空间运行多个进程来减少进程数量可以提高性能. 事实上, CLR提供让多个managed程序运行在一个进程空间的能力. 每个managed程序被称为AppDomain. 默认情况下,每个managed EXE将运行在它自己独立的进程空间, 只包含一个AppDomain. 但是,一个hosting了CLR的进程 (比如IIS)可以在一个OS进程中运行多个AppDomain.
16. 一些特定的FCL namespaces:
Namespace |
Description of contents |
System.Diagnostics |
调试程序和help instrument的Types |
System.Drawing |
操作2-D图形的Types;通常为Windows Forms 程序和创建用于Web Forms page显示的图片 |
System.EnterpriseServices |
用于transactions管理, queued components, object pooling, JIT activation, security, 和其他使得erver端managed code更有效的Type |
System.Management |
通过Windows Management Instrumentation (WMI)来管理enterprise上其他计算机的Type |
System.Net |
网络链接的Type |
System.Reflection |
用来解析metadata并且late binding到types及其成员的type |
System.Runtime.InteropServices |
允许managed代码执行unmanaged OS 平台功能(例如 COM 组件和Win32 DLL函数)的Type |
System.Runtime.Remoting |
容易type远程调用的type |
System.Runtime.Serialization |
允许对象实例永久保存到一个Stream或从中读取并解析的type |
System.Security |
用来保护数据和资源的type |
System.Text |
支持文本多重编码(比如ASCII或UNICODE)切换的type |
System.Threading |
用来处理对资源的异步或同步操作的type |
System.Xml |
处理XML schemas和data的type |
System.Web.Services |
用来创建XML Web services的type |
System.Web.UI |
用来创建Web Forms的type |
System.Windows.Forms |
用来创建Windows GUI应用程序的type |
System.ServiceProcess |
用来创建可被SCM操作的Windows service 的type |
17. Common type system (CTS) 标准定义了一个type应该如何定义以及如何表示. 一个type的成员可以是:
a. Field: 一个数据变量, 这个对象的状态的一部分.
b. Method: 一个执行在对象上的函数.
c. Property: 对调用者来说, 这个成员相当于field, 当type的实现者来说, 这个成员相当于一个method.
d. Event: 一个event定义了一个对象和对它感兴趣的对象直接的通信机制.
18. CTS同样指定了type和其成员的可见度的规则 (C#和VB中默认为Assembly):
a. Private: 这个成员只能被同一个type中的成员访问.
b. Family: 这个成员可以被type以及它的继承type访问, C#的关键字是protected.
c. Family and assembly: 这个成员可以仅可以被定义在同一个assembly的type的继承type访问, C#和VB都没有提供对应的定义机制.
d. Assembly: 这个成员可以被同一个assembly的所有代码访问, C#关键字是internal. VB中关键字是Friend.
e. Family or assembly: 这个成员可以被继承的type访问, 也可以被同一个assembly中任何代码访问; C#关键字是protected internal. VB关键字是Protected Friend.
f. Public: 这个成员可被任意访问.
19. CTS定义了type继承的规则, virtual函数的对象的生存期间等等. CTS的好处就是不论使用何种变成语言, 但是type的形式是相同的. 另一个CTS的规则是所有的type必须(最终)从一个预先定义的type中继承: System.Object.
20. Common Language Specification (CLS) 定义了编译器生产厂商开发.NET平台上编译器所必须支持的最小功能集合. 其中的关联是: 开发语言是CLR/CTS的子集,是CLS的超集 (当然不必是同一个超集).
21. CLR支持三种interoperability scenarios:
a. Managed代码调用一个unmanaged DLL中的函数; 这个功能通过一个P/Invoke (全称: platform Invoke) 的机制实现.
b. Managed代码可以使用一个现有的COM组件 (服务); 使用这个COM组件的type library, managed代码可以直接访问组件中的type.
c. Unmanaged 代码可以使用managed代码 (服务).
22. 另一个特别的scenario, Microsoft C++编译器支持一个全新的/clr 命令行开关. 这个开关使得编译器编译为IL代码,而不是native CPU指令. 如果代码中使用了inline assembly (__asm关键字), 动态参数, 使用了跳转语句或者包含一些intrinsic routines (例如 __enable, __disable, _ReturnAddress, 和_AddressOfReturnAddress),这个开关将无法生产IL代码.
Chapter 2: Building, Packaging, Deploying, and Administering Applications and Types
1. C#编译器的一些编译开关 (csc.exe):
a. /r: 引用指定的assembly, 一些经常使用的assemblies默认就被加入引用.
b. /t: target file. exe 表示Console应用程序, winexe表示GUI程序, library为DLL文件, module为一个不包含任何manifest metadata表的DLL PE文件.
c. /nostdlib: 禁止自动引用assemblies.
d. /addmodule: 增加一个指定的module到生成的assembly文件.
e. /resource: 在生成的assembly文件嵌入资源文件, 只更新ManifestResourceTable.
f. /linkresource: 在ManifestResourceDef和FileDef manifest表中增加一个指向独立存在的资源文件的项.
g. /win32res: 快速而简便的嵌入一个后缀名为.res的文件.
h. /win32icon: 在PE文件中嵌入一个标准的win32图标.
i. /noconfig: 忽略本地和全局的csc.rsp resource 文件.
2. 一个managed PE文件包含4个主要部分: PE header, CLR header, metadata, 和IL. CLR header是一小块数据信息,包含了Module所需的CLR的信息, 这个header 包含了模块生成metadata的主要和次要版本, 一些标志位, 一个MethodDef token指明了这个模块的入口函数, 如果这个模块是CUI或GUI可执行文件的话, 和一些可选的strong name 数字签名. 最后, 这个header还包含了模块包含metadata 表的大小和偏移值. 在头文件CorHdr.h中, 这个CLR Header的格式通过IMAGE_COR20_HEADER所定义.
3. Metadata是一个二进制数据块, 包含了数个表. 存在三个不同类型的表: definition表, references表, 和manifest表.
4. Metadata中通用的definition表:
Metadata中 Definition表名 |
Description |
ModuleDef |
表中每项代表一个模块. 每项都包含了模块的文件名和扩张名(不包括路径), 还包含了模块的版本ID (由编译器生成的GUID). 这使得文件可以被重命名时候仍然保存了原有的名字. |
TypeDef |
表中每项代表了模块的一个type. 每项都包含了type的名字, 继承type, 标志位 (比如public, private, 等等) 并指向MethodDef 表对应的位置, 指向FieldDef 表对应的位置, 指向PropertyDef表中对应的位置, 和EventDef表中对应的位置. |
MethodDef |
表中每项代表了模块中定义的每个方法. 每项都包含了方法的名称, 标志位(private, public, virtual, abstract, static, final, 等), signature, 和在模块中对应IL代码的便宜位置. 每项同样还指向ParamDef表中对应的位置. |
FieldDef |
表中每项代表了模块中定义的field. 每项都包含了名字,状态位和type. |
ParamDef |
表中每项代表了模块中定义的parameter. 每项包括了名字和标志位 (in, out, retval, 等). |
PropertyDef |
表中每项代表了模块中定义的property. 每项包含了名字, 状态位, type, 和backing field (可以为null). |
EventDef |
表中每项代表了模块中定义的even, 每项包含了名字和状态位. |
5. Metadata中通用的references表:
Metadata 中Reference表名 |
Description |
AssemblyRef |
表中每项代表了模块引用的每个assembly. 每项都包含了绑定到assembly的所需信息: assembly的名字 (没有路径和后缀名), 版本, culture, 和public key token (通常是一个小的hash value, 从publisher’s public key生成, 表明了referenced assembly的publisher). 每项还包含了一些状态位和一个hash value. 这个hash value是referenced assembly二进制内容的checksum. |
ModuleRef |
表中每项代表了模块中type引用的每个PE 模块. 每项包含了模块的文件名和扩展名 (没有路径). 该表被用来绑定其他模块中定义的type. |
Type Ref |
表中每项代表了模块中引用的type. 每项包含了type的名称和找到该type的引用. 如果这个type由另外一个type实现, 那这个引用就是一个TypeRef项. 如果这个type由另外一个module实现, 那这个引用就是一个ModuleDef项. 如果这个type由另外一个引用的module实现,那么这个引用就是一个ModuleRef项. 如果这个type是由另外一个引用assembly的实现, 那么这个引用就是一个AssemblyRef项. |
MemberRef |
表中每项代表了模块中引用的member(包括了fields, methods, property和event). 每项都包含了member的名称和signature, 并指向一个定义该member的type的TypeRef项. |
6. 查看metadata表, 执行这个命令行: ILDasm /adv App.exe
7. 一个程序可以通过配置程序的配置文件中的codeBase项来指定模块的下载地址. 这个codeBase项指明了能找到assembly文件的URL.
8. 创建一个assembly, 必须选择一个PE文件来包含manifest. 也可以创建一个独立的PE文件来只包含manifest.
9. Manifest Metadata 表
Manifest Metadata表名 |
Description |
AssemblyDef |
表中每项代表了一个assembly. 每项包含了assembly名称 (不包括路径和扩展名). 版本(major, minor, build, 和revision), culture, 状态位, hash算法, 和publisher’s public key (可以为null). |
FileDef |
表中每项代表了assembly的每个PE和资源文件. 每项都包含了文件名和后缀名 (无路径), hash value, 和标志位. 如果这个assembly只包含它自己, 这个FileDef表没有项. |
ManifestResourceDef |
表中每项代表了作为assembly一部分的每个资源. 每项包含了资源名, 标志位(public, private), 和一个指向FileDef表中该资源的索引. 如果这个资源不是独立文件 (比如.jpe或.gif), 那它是PE文件中的一个stream. 对一个嵌入的资源, 每项同样包含PE文件中stream的偏移值. |
ExportedTypeDef |
表中每项代表了assembly中所有PE module的每个exported type. 每项都包含了type的名称, 指向FileDef表的一个索引, 和一个TypeDef表的一个索引. 为了保存文件空间, 包含了manifest文件的exported type不会在该表中重复出现, 因为metadata的 TypeDef表中已经存在这些内容. |
10. 值得补充的是, assembly中包含了manifest文件 同样在AssemblyRef表存在. Manifest的存在使得assembly是self-describing.
11. 严格来讲, metadata tokens (参见ILDASM.exe的report) 都是4字节的值. 高位字节标明了token 的类型(0x01=TypeRef, 0x02=TypeDef, 0x26=FileRef, 0x27=ExportedType). 可以在CorHdr.h文件中查看CorTokenType enumerated type.
12. Assembly Linker (al.exe) 工具可以生成一个EXE或DLL格式的PE文件, 该文件只包含一个manifest来标明其他模块的type. 如果创建一个EXE, 必须使用AL.exe的/main编译开关来指定入口方法.
13. Assembly Linker (al.exe) 支持/embed编译开关将资源文件嵌入PE文件; manifest的 ManifestResourceDef表将被更新来包含新的资源. 另一个编译开关/link[resource]同样指定资源文件, 它会更新ManifestResourceDef 和FileDef表, 资源文件并未嵌入assembly的 PE文件. 它同样支持/win32res和/win32icon, 类似于C#编译器.
14. 创建一个assembly, 必须设置版本字段(这些字段可以通过OS explorer查看) 使用custom attributes 可以在源代码上指定assembly 级的版本, 如: [assembly:AssemblyCopyright(“Copyright ©”)];
15. 版本资源字段和AL.exe编译开关以及custom attributes:
Version Resource |
AL.exe switch |
Custom Attribute/Comment |
FILEVERSION |
/fileversion |
System.Reflection.AssemblyFileVersionAttribute |
PRODUCTVERSION |
/productversion |
System.Reflection.AssemblyInformationalVersionAttribute |
FILEFLAGSMASK |
(none) |
总是VS_FFI_FILEFLAGSMASK (在WinVer.h定义为0x0000003F) |
FILEFLAGS |
(none) |
总是 0 |
FILEOS |
(none) |
目前总是 VOS__WINDOWS32 |
FILETYPE |
/target |
是/target:exe或/target:winexe就为VFT_APP; 是/target:library就为VFT_DLL |
FILESUBTYPE |
(none) |
总是VFT2_UNKNOWN (对VFT_APP和VFT_DLL这个字段无意义.) |
AssemblyVersion |
/version |
System.Reflection.Assembly-VersionAttribute |
Comments |
/description |
System.Reflection.Assembly-DescriptionAttribute |
CompanyName |
/company |
System.Reflection.AssemblyCompanyAttribute |
FileDescription |
/title |
System.Reflection.AssemblyTitleAttribute |
FileVersion |
/version |
System.Reflection.AssemblyVersionAttribute |
InternalName |
/out |
设置为输出文件的名字 (无后缀名) |
LegalCopyright |
/copyright |
System.Reflection.AssemblyCopyrightAttribute |
LegalTrademarks |
/trademark |
System.Reflection.AssemblyTrademarkAttribute |
OriginalFilename |
/out |
设置为输出文件的名字 (无路径) |
PrivateBuild |
(none) |
总是空白 |
ProductName |
/product |
System.Reflection.AssemblyProductAttribute |
ProductVersion |
/productversion |
System.Reflection.AssemblyInformationalVersionAttribute |
SpecialBuild |
(none) |
总是空白 |
16. 所有的版本数字格式都相同: 每个都包含了四个部分, major number, minor number, build number, revision number. 前两个数字组成了 “public perception”的版本. 第三个数字说明了assembly被build的次数, 如果一个assembly每天build一次, 那应该每天都增加build number. 最后一个数字标明了build的revision, 比如一些情况下这个assembly在一天内被build了两次, 可能是为了修正hot bug, 这时revision number就应该增加.
17. 一个assembly包含了三个版本数字.
a. AssemblyFileVersion, 这个版本数字存储在Win32 version资源中. 这个数字仅仅做参考; CLR在任何情况下都不检查这个数字. 一般来说, 这个数字是设置为所有人都能查看的版本信息.
b. AssemblyInfomationVersionAttribute, 这个版本数字同样存储在Win32 version 资源中, CLR也同样不会检查这个数字. 这个数字的存在只是为了标明包含这个assembly的product的版本.
c. AssemblyVersion, 这个版本数字存储在AssemblyDef manifest metadata表中. CLR 在绑定strongly named assemblies会检查这个数字. 这个数字很重要,它将在唯一标志一个assembly时使用.
18. 类似于版本数字, assembly使用culture作为唯一性的一部分. Culture通过一个包含primary和secondary tag的字符串来指定. 未指定culture的assembly被认为是culture neutral. 包含了一个特定culture的Assembly被称为satellite assemblies. AL.exe提供了一个/c[ulture]:text的编译开关来实现culture的绑定. 当布置一个satellite assembly, 应该将它放在跟其Culture名相同的子文件夹下. 例如, 如果一个程序的基本目录是C:\MyApp, 那么一个U.S. English的satellite assembly就应该放在C:\MyApp\en-US 子目录下. 运行时, 可以通过System.Resources.ResourceManager类来访问这些satellite assembly.
19. 为了控制一个应用程序, 可在程序的文件夹中设置一个configuration file. CLR解析这个文件中的内容并应用这个政府来装载assembly文件. 这些configuration files (名字是由应用程序的名字加.config的后缀名组合) 由XML组成, 可以跟程序或当前机器绑定. 例如, 可以使用multiple semicolon-delimited paths来填充privatePath下的probing, 这将指定一个包含其他assembly的子目录. 当然也可以编写代码解析该文件的内容.
20. 当.Net framework被安装时候, 它生成了一个Machine.config文件. CLR的每个版本包含一个Machine.config. 存放在: C:\WINDOWS\Microsoft.NET\Framework\version\CONFIG
21. 因为程序的configuration file是基于XML的, .NET生成了一个更易用的GUI工具. 这个GUI工具作为一个Microsoft Management Console (MMFC) snap-in实现的, 可以通过Control Panel打开.
Chapter 3: Shared Assemblies
1. .NET Framework支持两种类型的assembly: weakly named assemblies和strongly named assemblies. Weakly named assemblies和strongly named assemblies拥有相同的结构 – 使用相同的PE文件格式. 两者的真正的区别在于一个strongly named assembly 被publisher’s public/private密钥对签名, 唯一标志了assembly的发布者. 一个assembly 可以通过两种方式发布: privately或globally. Weakly named assembly不能globally发布.
2. 一个strongly named assembly包含了四个能唯一标志该assebly的attribute: 一个文件名 (无后缀名), 一个版本数字, 一个culture identity, 和一个public key token (public key的hash value的最后8字节). 一个strongly named assembly由发布者的private密钥签名. 使用System.Reflection.AssemblyName类可以很容易的创建一个assebly的名称和获取该名称的4个组成部分, 这个类的提供一些instance properties比如CultureInfo, FullName, 和一些instance method, 比如GetPublicKey, Get PublicKeyToken.
3. 使用Strong Name Utility (SN.exe) 可以生成一个密钥文件: sn –k mycompany.keys. 查看密钥的public key number (这通常非常大), 使用: sn –tp mycompany.keys. 一个public key token 是public key的一个64位的hash value. 通过使用AL.exe的/keyfile编译开关或在代码中使用System.Reflection.AssemblyKeyFileAttribute attribute, 例如: [assembly:AssemblyKeyFile(“MyCompany.keys”]都可以很容易生成一个strongly named assembly. 注意,assembly中只有包含manifest的文件被签名.
4. 当创建一个strongly named assembly:
a. Assembly的FileDef manifest metadata表包含了组成assembly的所有文件的列表. 类似于每个文件的名称都被加入manifest, 文件的内容被hash处理并且hash value跟文件名一起存储在FileDef表中. 可以覆盖默认的hash算法,使用AL.exe的/algid编译开关或名为System.Reflection.AssemblyAlgIDAttribute的custom attribute. 默认将使用SHA-1算法.
b. 当包含manifest的PE被创建, 该PE file的全部内容被hash, 这里的hash算法只能是SHA-1. 生成的hash value – 通常是100或200字节 – 被发布者的private密钥签名, 同时生成的RSA数字签名被存放在PE文件的一个预留块中.
c. 该PE文件CLR header被更新,使之能反映出数字签名被存在在文件中.
d. 发布者的public密钥被完整的包含在PE文件的AssemblyDef manifest metadata表中.
5. 用来存放strongly named assembly的文件夹被称为global assembly cache (GAC), 该文件夹是: C:\Windows\Assembly\GAC. GAC文件夹是有结构的: 它包含许多子文件夹, 有一个专门用来创建子文件夹名称的算法. 不应该手动复制assembly文件到GAC, 应该使用一个GACUtil.exe的工具来导入和操作GAC的assembly. 例如, /i执行开关将安装一个assembly, /u执行开关卸载assembly.
6. 真实的开发环境中推荐使用GACUtil.exe的/ir执行开关代替/I, 用/ur执行开关卸载assembly. 这个/ir执行开关被包含在Windows的安装和卸载engine中. 基本上, 它用来告诉系统那个程序需要这个assembly并把程序和这个assembly绑定. 成功导入GAC后原文件可以删除, 注意GACUtil.exe并没有包含在.NET framework redistributable package中, 你必须使用Windows Installer.
7. 安装.NET Framework时, 一个Explorer shell的扩展 (ShFusion.dll) 也被安装. 这个shell 扩展同样知道GAC的内部结构, 并且把GAC中内容以易读懂的方式展现出来. 可以把一个包含了manifest的assembly文件拖入Explorer的窗口, 这个shell扩展会把这个assembly安装到GAC.
8. GAC文件夹的内部结构可以通过CMD.exe的命令DIR显示. 真实的子文件夹名字的格式是 “(Version)_(Culture)_(PublicKeyToken)”. GAC通过这样方式避开了DLL hell.
9. 使用CSC.exe的/reference编译开关指定一个引用的assembly, 如果指定了完整路径这个assembly文件会被直接装载, 否则CSC.exe会在如下路径中查找该文件:
a. Working文件夹
b. 包含CLR和编译器自身的文件夹. MsCorLib.dll就是从这个文件夹中装载的. 这个文件夹通常是: C:\WINDOWS\Microsoft.NET\Framework\v2.0.
c. 通过CSC.exe的/lib编译开关指定的所有文件夹.
d. 通过LIB 环境变量指定的所有文件夹.
10. 安装.NET Framework, 它会安装两份Microsoft’s assembly文件. 一份安装在CLR文件夹, 另外一份安装在GAC. CLR文件夹中的这份用来供用户创建自己的assembly. GAC的文件则是在run time装载用的. CSC.exe之所以不在GAC查找引用的assembly是因为那样的话必须指定一个long, ugly的路径.
11. 一个response file是包含了一组编译器命令行开关的文件文件. 当执行CSC.exe, 编译器打开response files并使用其中的命令行开关. 通过使用@符号加response file作为命令行参数会使得编译器启用该response file. 比如: csc.exe @MyProject.rsp code1.cs code2.cs. C#编译器支持同时输入多个response file. 编译器除了使用用户输入的response file, 它还自动查找一个名叫CSC.rsp的文件. 当CSC.exe启动, 它会在当前文件夹中查找一个本地的CSC.rsp文件 – 应该把项目的所有设置都写在该文件中. 编译器还会在CSC.exe自身的文件夹中查找一个全局的CSC.rsp文件. 安装.NET Framework时, 它安装了一个默认的CSC.rsp 文件, 可以使用/noconfig编译开关让编译器忽略全局和本地的csc.rsp文件.
12. 使用一个private的密钥Sign一个文件可以保证assembly的拥有者能被对应的public密钥认证. 当一个assembly被安装到GAC, 系统会对包含manifest的文件求hash, 并和该PE文件中的用RSA数字签名过的hash value进行比较. 如果相等, 文件内容没有被修改并且拥有了与发布者的private密钥相配对的public密钥. 系统还会对assembly的其他文件求hash, 并和保存在manifest文件中的FileDef表的hash value进行比较.
13. 当一个程序需要绑定一个assembly, CLR使用assembly的特性来定位在GAC中位置. 如果找到了这个assembly, 包含manifest的文件被装载. 如果不能在GAC找到, CLR在程序的base文件夹中查找, 接着是在程序的配置文件中的指定的所有private路径. 然后, 如果该程序是通过MSI来安装, CLR会启动MSI来查找这个assembly. 如果在任何位置都不能找到该assembly, 将抛出一个exception.
14. 当strongly named assembly文件是从本地而不是GAC中装载时 (通过定义配置文件中的codeBase项), CLR会比较对应的hash value. 也就是说每次程序启动时都会对该文件求hash, 而GAC中的文件只在安装进GAC的时候才会.
15. SN.exe使用Windows本身提供的Crypto API来生成密钥文件.
16. Delayed sign允许只使用public密钥来创建一个assembly. 使用public密钥能使得引用该assembly的其他assembly能在它们的AsemblyRef metadata项填入正确的public 密钥值. 它同样允许被导入GAC的内部结构中. 但是使用delayed signing的assembly会失去文件的篡改保护.
17. 使用delay signing, 必须使用AssemblyKeyFileAttribute和DelaySignAttribute. 如果使用AL.exe, 可以使用/keyf[ile]和/delay[sign]命令行开关.
18. 以下步骤解释了如何使用delay sign技术:
a. 开发一个assembly, 在源代码中使用以下两行代码指定public密钥文件:
[assembly:AssemblyKeyFile(“MyCompanyPublicKey.keys”)]
[assembly:DelaySign(true)]
b. 创建assembly以后, 执行以下命令来安装该文件倒GAC, 创建引用该assembly的其他assembly. 注意这个命令只能执行一次; 这个命令不是每次创建assembly都必须执行的: sn –Vr MyAssembly.dll.
c. 当准备打包和发布这个assembly, 使用private密钥文件并执行一下命令: sn –r MyAssembly.dll MyCompanyPrivateKey.keys.
d. 为了测试, 打开verification过程, 使用一下命令: sn –Vu MyAssembly.dll.
19. 为了保证密钥安全, 必须保证这些密钥的值不能存放在硬盘的文件中. Cryptographic service providers (CSPs) 提供了一个 “containers”来抽象这些密钥的真实的存放位置. 如果public/private密钥对存放在一个CSP container, 不要使用AssemblyKeyFileAttribute 或者AL.exe的/keyf[ile]开关. 使用 System.Reflection.AssemblyKeyNameAttribute 或AL.exe的/keyn[ame]开关, 当使用SN.exe为delay signed的assembly增加private密钥 时, 使用–Rc 开关代替–R开关.
20. CLR可以装载在不同位置中的同名文件到同一个地址空间. 这叫做side-by-side execution, 是解决Windows “DLL hell” 问题的核心组件.
21. 解析一个引用type时, CLR在一下三个位置中查找该type:
a. 同一文件, 在编译就确定了在同一文件中的type (也称为as early bound).
b. 同一assembly的不同文件, runtime确保被引用的文件存在于assembly的manifest的FileRef 表中. CLR接着在包含assembly’manifest文件所在的文件夹, 其hash value i会检查以保证文件的完整性, type的成员被找到, 系统会继续仔细执行.
c. 不同assembly中不同文件, 当一个引用的type存在不同的assembly’的文件中, CLR装载包含被引用assembly manifest的文件.
22. 应用程序的XML配置文件:
a. probing 项: 使用 privatePath来指定查找weakly named assembly的子文件夹. 例如, 增加两个子文件夹: <probing privatePath=”SubFolder1;bin\subdir” />. 但是对strongly named assembly, CLR在GAC或codeBase项指定的URL中查找.
b. dependentAssembly项: 使用assemblyIdentity, bindingRedirect, publisherPolicy, codeBase等项来控制程序的行为.
23. 编译一个method时, CLR检查其所引用的type和member. 通过这个信息, runtime在创建调用assembly时就得出那些assembly将被引用. CLR随后检查程序配置文件中assembly并执行其中的版本跳转. 接着CLR在机器中的Machine.config文件查找并执行其中的版本好跳转.
24. 一个assembly的发布者可以使用一个policy来发布修正bug或者增加特性的新版本. 可以增加一个配置文件到assembly使之称为publisher policy assembly. 使用一下名利: AL /out:policy.1.0.MyAssembly.dll /version:1.0.0.0 /keyfile:MyKey.keys /linkresource:MyAssembly.config. 其中/out开关使得AL.exe创建一个新的只包含manifest的PE文件, 但是这个文件的名字非常重要. 名字的第一部分, Policy, 通知CLR这个publisher policy assembly被应用到所有的版本为1.0的这个assembly上. Publisher policy只能应用到一个assembly的主要版本和次要版本上. /version开关指明了这个publisher policy assembly的版本; 这个版本跟assembly本身没有任何关系. 这个publisher policy assembly必须安装到GAC中. 而这个例子中的MyAssembly assembly却必须安装到GAC.
25. 如果administrator希望CLR忽略所有的publisher policy assembly, 那administrator可以编辑程序的配置文件并增加下面的项: <publisherPolicy apply=”no” />.
26. 当一个Console或Windows Forms application在一个OS的user account运行时, CLR 保存了应用程序真正装载的assembly的记录; 这个assembly装载信息存在于内存中并会在程序终止时写入文件. 写入的文件通常在下面的文件夹中: C:\Documents and Settings\UserName\Local Settings\Application Data\ApplicationHistory.