在我们进入章节之前,我们讨论一下生成、打包和部署你的应用程序和应用程序类型必须的步骤。在这章里,我关注的是如何为你的应用程序的用途生成程序集。在第三章,“共享程序集合和强命名程序集”,我会涉及你需要了解的高级概念,包括如何生成和使用包含类型的程序集,这些程序集将被多个应用程序共享。在这两章中,我也会讨论管理员可以影响执行应用程序和它的类型的方式。
如今,应用程序由几个类型组成,通常由你和微软创建。此外,有很多组件供应商创建和出售类型,其它公司购买可以减少软件项目开发时间。假如这些类型是用以CLR为目标的语言开发的,那么它们可以全部一起无缝的工作;一种语言写的一个类型可以使用另一个类型做为它基类并且不关心基础类型是用什么语言开发的。
在这一章中,我将解释这些类型为了部署怎么生成和打包到文件。在这个过程中,我将带你回顾一些.NET Framework解决的问题。
.NET Framework Depoyment Goals(.NET Framework部署目标)
多年以来,Windows得到了一个不稳定和繁复的名声。这个名声是否名副其实要看很多不同的因素。首先,所有的应用程序使用微软或其他供应商的动态链接库(DLLs)。因为一个应用程序从各种各样的供应商执行代码,开发者的任何一段代码都不能100%的确定别人是怎么使用它的。即使这种类型的交互存在各种各样潜在的麻烦,事实上,这些问题一般不会出现因为应用程序在部署前都会进行测试和调试。
无论如何,当一家公司决定更新它的代码和装载新的文件时用户运行经常会遇到问题。这些新文件被假设能向后兼容之前的文件,但是谁可以确定?事实上,当一家供运商更新它的代码时,它一般发现它不可能重新测试和调试所有已装载的应用程序以确保有不希望的影响。
我相信每个读这本书的人都经历过这个问题的衍生:当安装一个新的应用程序,你发现它以某种方式破坏了一个已经安装好的应用程序。这种困境是众所周知的“DLL地狱”。这种类型不稳定在一般的电脑使用者心里和大脑中都留下了恐惧的烙印。结果是用户不得不小心考虑是否在他们的机器上安装一个新的软件。就我自己来说,我决定不尝试安装应用程序走出恐惧,它可能会对我依赖的应用程序产生不利的影响。
第二个原因,给前面提及的Windows名声做出贡献的是安装的复杂性。如今,当安装了大多数应用程序,它们影响系统所有的部分。例如,安装一个应用程序引起文件被拷贝到不同目录,更新注册表,在你的桌面和开始菜单/桌面安装快捷方式。这带来的问题是应用程序没有分离为一个单独的实体。你不能容易的备份应用程序因为你必须拷贝应用程序的文件和注册的相关部分。此外,你不能容易的从一个机器移动应用程序到另一个机器。你必须在运行一次安装程序,所有文件和注册表才能设置正确。最终,你不能容易的卸载或者移除应用程序而没有这种讨厌的感觉,应用程序的一些部分还潜伏在你的机器上。
第三个原因和安全有关。当应用程序安装好了,伴随着它们的是各种各样的文件,其中很多是通过不同的公司写的。此外,网页应用程序经常有代码(就像ActiveX控件)这样下载,用户甚至没有察觉代码已经安装在他们的机器上了。如今,这份代码可以执行任何操作,包括删除文件或发邮件。用户害怕安装新的应用程序是正确的,因为它们会引起潜在的损害。为使用户舒服,系统中必须建立安全,用户可以明确地允许或者不允许不同的公司开发的代码访问他们的系统资源。
.NET Framework把DLL地狱问题放在重要位置,就像你将在这章和第三章里看到的。.NET Framework在修复应用程序状态分散在用户硬盘的各个角落问题走了很远。例如,不像COM,类型不需要在在注册表中设置。不幸的是,应用程序依然需要快捷方式链接。至于安全,.NET Framework包含一个叫做代码访问安全(code access security)的安全模型。鉴于Windows安全是以用户身份为基础的,代码访问安全允许机主设置权限,从而控制下载的组件可以做什么。一个主机应用程序像Microsoft SQL Server只给代码少量授权,然而一个本地安装(自我寄宿)的应用程序运行时拥有所有权限。正如你将看到的,.NET Framework使用户可以控制安装什么和运行什么,总之,对于控制机器,Windows从未做的这么好。
Building Types into a Module(类型生成一个模块)
在这个场景,我将给你看怎么把你源文件(包含不同的类型)转换成一个可以部署的文件。让我们从仔细查看下面的简单应用程序开始。
public sealed class Program { public static void Main(string[] args) { System.Console.WriteLine("Hi"); } }
这个应用程序定义了一个类型,叫做Program。这个类型有一个单独的公共的、静态的叫做Main的方法。在Main中应用了一个由微软实现的叫做System.Console.System.Console的类型,并且在MSCorLib.dll文件中实现了这个类型的方法的中间语言(IL)代码。所以我们的应用程序定义了一个类型同时也使用了另一个公司的类型。
生成这个简单的应用程序,把之前的代码放到一个源代码文件中,叫做,Program.cs,然后执行以下命令行:
csc.exe /out: Program.exe /t:exe /r:MSCorLib.dll Program.cs
这个命令行告诉C#编译器发布一个叫做Program.exe(/out: Program.exe)的可执行文件。这个类型文件产生一个Win32控制台应用程序(/t[arget]:exe)。
当C#编译器处理源文件,它看到代码引用System.Console类型的WriteLine方法。这时候,编译器要确定这个类型在哪里存在,它有一个WriteLine方法,并且参数被传递给这个方法,且匹配方法期望的参数。因为这个类型不是在C#中定义的,为了使C#编译器开心,你必须给它一个程序集使它能够解决外部类型引用。在之前提到的命令行,我已经包括了/r[eference]:MSCoreLib.dll开关,它告诉编译器通过MSCorLib.dll文件定义的程序集查找外部类型。
MSCorLib.dll是一个特殊的文件,它包含所有的核心类型:Byte,Char,String,Int32等等。事实上,这些类型是如此频繁的被使用,所以C#编译器自动引用MSCorLib.dll程序集。换句话说,下面的命令行(省略/r 开关)和上面的命令行结果是一样。
csc.exe /out: Program.exe /t:exe Program.cs
此外,因为/out: Program.exe和/t:exe命令行开关也匹配C#编译器的默认选择,下面的命令行也会给出相同的结果。
csc.exe Program.cs
假如,为了一些原因,你真的不想要C#编译器引用MSCorLib.dll程序集,你可以使用/nostdlib开关。当建立MSCorLib.dll程序集时微软使用这个开关。例如,当CSC.exe尝试编译Program.cs文件时以下的命令行将会产生一个错误因为System.Console类型定义在MSCorLib.dll中。
csc.exe /out: Program.exe /t:exe /nostdlib Program.cs
现在,让我们近一点看C#编译器生成的Program.exe文件。这个文件实际上是什么?好吧,首先,它是一个标准的可移植执行体(portable executable,PE)文件。这意味着一个运行32位或者64位版本Windows的机器可以加载这个文件并用它做点什么。Windows支持三种类型的应用程序。建立一个控制台用户界面(console user interface,CUI)应用程序,指定/t:exe开关;建立一个图形用户界面(graphical user interface,GUI)应用程序,指定/t:winexe开关;建立一个Windows商店App,指定/t:appcontainerexe开关。
Response Files(响应文档)
在搁置关于编译的开关的讨论前,我要花一点时间谈谈响应文档。一个响应文件是一个包含一个编译器命令行开关集合的文本文件。当你执行CSC.exe,编译器打开响应文档并使用详细定义的开关就像命令行传递给CSC.exe的一样。你命令编译器通过在命令行前加@符号指定响应文件的名字使用指定的响应文件。例如,你有一个叫做MyProject.rsp的响应文件包含以下文本。
/out:MyProject.exe
/target:winexe
为了CSC.exe使用这些设置,你可以使用以下内容调用。
csc.exe @MyProjext.rsp CodeFile1.cs CodeFile2.cs
这告诉C#编译器输出文件时什么名字和要创建什么目标类型。就你看见的,响应文档很方便因为你不需要每次编译你的项目时手动给命令行参数表达你的意愿。
C#编译器支持多个响应文档。除了文档外你显式指定在命令行的,编译器自动在CSC.rsp文档中查找。当你运行CSC.exe,它查找包含CSC.exe文件的目录以得到全局CSC.rsp文件。你需要应用到你的项目的设置应该都放在这个文件中了。编译器集合和使用在这些响应文档中的设置。假如你在本地和全局响应文档中有冲突设置,本地的设置重载在全局响应文件中的设置。同样的,任何在命令行显式传递的设置从本地响应文件重载设置。
当你安装.NET Framework,它在%SystemRoot%\Microsoft.NET\Framework(64)\vX.X.X (X.X.X是你安装的.NET Framework的版本)目录默认安装了全局CSC.rsp文件。最近的版本文件包含以下开关。
# This file contains command-line options that the C# # command line compiler (CSC) will process as part # of every compilation, unless the "/noconfig" option # is specified. # Reference the common Framework libraries /r:Accessibility.dll /r:Microsoft.CSharp.dll /r:System.Configuration.dll /r:System.Configuration.Install.dll /r:System.Core.dll /r:System.Data.dll /r:System.Data.DataSetExtensions.dll /r:System.Data.Linq.dll /r:System.Data.OracleClient.dll /r:System.Deployment.dll /r:System.Design.dll /r:System.DirectoryServices.dll /r:System.dll /r:System.Drawing.Design.dll /r:System.Drawing.dll /r:System.EnterpriseServices.dll /r:System.Management.dll /r:System.Messaging.dll /r:System.Runtime.Remoting.dll /r:System.Runtime.Serialization.dll /r:System.Runtime.Serialization.Formatters.Soap.dll /r:System.Security.dll /r:System.ServiceModel.dll /r:System.ServiceModel.Web.dll /r:System.ServiceProcess.dll /r:System.Transactions.dll /r:System.Web.dll /r:System.Web.Extensions.Design.dll /r:System.Web.Extensions.dll /r:System.Web.Mobile.dll /r:System.Web.RegularExpressions.dll /r:System.Web.Services.dll /r:System.Windows.Forms.Dll /r:System.Workflow.Activities.dll /r:System.Workflow.ComponentModel.dll /r:System.Workflow.Runtime.dll /r:System.Xml.dll /r:System.Xml.Linq.dll
因为全局CSC.rsp文件引用了所有列出的程序集,你不需要通过C#编译器的/reference开关显式的引用这些程序集。这个响应文件给开发者提供了很大的便利因为它允许开发者使用定义在各种微软发布程序集中的类型和命名空间而不需要每次编译时指定一个/reference编译器开关。
引用所有程序集编译器会慢一点。但是如果你的源代码没有引用定义在这些程序集中的类型和成员,对于生成程序集文件没有任何影响,也不会影响运行时的执行性能。
注意 当你使用/reference编译器开关引用一个程序集时,你可以给特殊的文件指定一个完整的路径。但是,假如你没有指定路径,编译器会在以下列出的地方搜索文件(按列出的顺序): *工作目录。 *包含CSC.exe文件的目录。MSCorLib.dll就是从这个目录中获得的。路径看起来有点像这个:%SystemRoot%\Microsoft.Net\Framework\v4.0.#####。 *使用/lib编译器开关指定的任何名录。 *使用LIB环境变量指定的任何目录。 |
当然,欢迎你在全局CSC.rsp文件中添加你自己的开关是你的生活更简单,但是这么做,当复制生成环境到不同的机器时会变的更困难——你不得不记住在每个生成机器上以相同的方式更新CSC.rsp。另外,你可以告诉编译器忽略本地和全局CSC.rsp文件通过指定/noconfig命令行开关。
A Brief Look at Metadata(简单查看元数据)
现在我们知道我们创建了什么类型的PE文件。但是实际在Program.exe文件中是什么?一个托管的PE文件有4个主要的部分:PE32(+)头,CLR头,元数据和IL。PE32(+)是Windows期望的标准信息。CLR头是一小块信息指定模块需要CLR(托管模块)。头文件里包括CLR的主要和次要版本号,模块需要构建的:一些标记,一个MethodDef口令(以后会描述)指出模块的入口点假如模块是一个CUI,GUI,或者Windows Store可执行文件,和一个可选的强名称的数字信号(在第三章讨论)。最后,头文件包含构成模块的特定元数据表的大小和偏移量。你可以通过检测定义在CorHdr.h头文件中的IMAGE_COR20_HEADER查看CLR的确切格式。
元数据是由几个表组成的一个二进制块。表有三个分类:定义表,引用表,和载货单表。下表描述了一些在模块的元数据块中较为常见的定义表。
Common Definition Metadata Tables(常见定义元数据表)
Metadata Definition Table Name(元数据定义表名称) | Description(描述) |
ModuleDef(模块定义) | 是一个识别模块的条目。条目包括模块的文件名和扩展(无路径)和一个模块的版本ID(编译器在表单中创建的一个GUID)。这允许文件重命名当使用起始名称持续记录的时候。然而,非常不鼓励重命名文件,它可能会阻止CLR在运行时定位程序集,所以不要那么做。 |
TypeDef(类型定义) | 每个定义在模块中的类型的条目。每个条目包括类型的名称,基础类型,和标记(public,private,等等)和它在MethodDef表中的所有方、在FieldDef表中的所有字段、在PropertyDef表中的所有属性、在EventDef表中的所有事件的索引。 |
MethodDef(方法定义) | 每个定义在模块中的方法的条目。每个条目包括方法的名字,标记(private,public,virtual,abstract,static,final等等),签名,模块内的偏移量,在那可以找到它的IL代码。每个条目也可以参考ParamDef表条目,在那可以找到更多关于方法参数的信息。 |
FieldDef(字段定义) | 每个定义在模块中的字段的条目。每个条目包括标记(private,public,等等),类型和名称。 |
ParamDef(参数定义) | 每个定义在模块中的参数的条目。每个条目包括标记(in,out ,retval,等等),类型和名称。 |
PropertyDef(属性定义) | 每个定义在模块中的属性的条目。每个条目包括标记,类型和名称。 |
EventDef(事件定义) | 每个定义在模块中的事件的条目。每个条目包括标记和名字。 |
当编译器编译的源码时,你代码中定义的每件事都会引起一个条目的创建如上表所描述。元数据表也会被创建当编译器察觉类型,字段,方法,属性和事件是源代码引用。元数据创建包含一个引用表集合,保存了引用项目记录。下表显示了一些比较常见的引用元数据表。
Common Reference Metadata Tables(常用引用元数据表)
Metadata Reference Table Name(元数据引用表名称) | Description(描述) |
AssemblyRef(程序集引用) | 模块引用的每个程序集的条目。每个条目包括需要绑定到的程序集的信息:程序集的名称(没有路径和扩张),版本号,区域,和公共关键口令(一般是从发布者公开的关键字生成的哈希值,识别引用程序集的发布者)。每个条目也是一些标记和一个哈希值。这个哈希码倾向于作为应用程序集比特的校验码。CLR完全忽略这个哈希值将来可能会继续这样做。 |
ModuleRef(模块引用) | 实现这个模块引用的类型的每个PE的条目。每个条目包括模块的文件名称和扩张(没有路径)。此表用于绑定到类型实现不同模块的调用汇编模块。 |
TypeRef(类型引用) | 每个被模块引用的类型有一个条目。每个条目包括类型的名字和引用类型可以找到的地方。如果类型中实现另一种类型,引用将会指出一个TypeRef条目。如果类型在同一个模块中实现,引用将会指出一个ModuleRef条目。如果类型调用程序集在另一个模块中实现,引用将会指出一个ModuleRef条目。假如类型在不同的程序中实现,引用将会指出一个AssemblyRef条目。 |
MemberRef(成员引用) | 每个被模块引用的成员(字段和方法,和属性和事件方法一样)有一个条目。每个条目包括成员的名字和签名和指向TypeRef的条目。 |
还有很多表单我没有在上面两个表单中列出,而我只想给你一个编译器发布产生元数据信息种类的感觉。之前我提到的有一个货运单元数据表集合;我会晚一点在这章讨论这些。
大量的工具允许你检测在托管PE文件中的元数据。我经常使用的还是ILDasm.exe,IL反汇编程序。要看元数据表,执行以下命令行。
ILDasm Program.exe
这样ILDAasm.exe将会运行,加载Program.exe程序集。可以看到元数据在一个漂亮的、可读的表单里,选择View/MetaInfo/Show!菜单项(或者按Ctr+M)。以下信息将会出现。
=========================================================== ScopeName : Program.exe MVID : {42F16070-F580-468A-9925-70283A94D852} =========================================================== Global functions ------------------------------------------------------- Global fields ------------------------------------------------------- Global MemberRefs ------------------------------------------------------- TypeDef #1 (02000002) ------------------------------------------------------- TypDefName: Program.Program (02000002) Flags : [Public] [AutoLayout] [Class] [Sealed] [AnsiClass] [BeforeFieldInit] (00100101) Extends : 01000001 [TypeRef] System.Object Method #1 (06000001) [ENTRYPOINT] ------------------------------------------------------- MethodName: Main (06000001) Flags : [Public] [Static] [HideBySig] [ReuseSlot] (00000096) RVA : 0x00002050 ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT] ReturnType: Void No arguments. Method #2 (06000002) ------------------------------------------------------- MethodName: .ctor (06000002) Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886) RVA : 0x0000205e ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #1 (01000001) ------------------------------------------------------- Token: 0x01000001 ResolutionScope: 0x23000001 TypeRefName: System.Object MemberRef #1 (0a000012) ------------------------------------------------------- Member: (0a000012) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #2 (01000002) ------------------------------------------------------- Token: 0x01000002 ResolutionScope: 0x23000001 TypeRefName: System.Runtime.Versioning.TargetFrameworkAttribute MemberRef #1 (0a000001) ------------------------------------------------------- Member: (0a000001) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: String TypeRef #3 (01000003) ------------------------------------------------------- Token: 0x01000003 ResolutionScope: 0x23000001 TypeRefName: System.Reflection.AssemblyTitleAttribute MemberRef #1 (0a000002) ------------------------------------------------------- Member: (0a000002) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: String TypeRef #4 (01000004) ------------------------------------------------------- Token: 0x01000004 ResolutionScope: 0x23000001 TypeRefName: System.Reflection.AssemblyDescriptionAttribute MemberRef #1 (0a000003) ------------------------------------------------------- Member: (0a000003) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: String TypeRef #5 (01000005) ------------------------------------------------------- Token: 0x01000005 ResolutionScope: 0x23000001 TypeRefName: System.Reflection.AssemblyConfigurationAttribute MemberRef #1 (0a000004) ------------------------------------------------------- Member: (0a000004) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: String TypeRef #6 (01000006) ------------------------------------------------------- Token: 0x01000006 ResolutionScope: 0x23000001 TypeRefName: System.Reflection.AssemblyCompanyAttribute MemberRef #1 (0a000005) ------------------------------------------------------- Member: (0a000005) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: String TypeRef #7 (01000007) ------------------------------------------------------- Token: 0x01000007 ResolutionScope: 0x23000001 TypeRefName: System.Reflection.AssemblyProductAttribute MemberRef #1 (0a000006) ------------------------------------------------------- Member: (0a000006) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: String TypeRef #8 (01000008) ------------------------------------------------------- Token: 0x01000008 ResolutionScope: 0x23000001 TypeRefName: System.Reflection.AssemblyCopyrightAttribute MemberRef #1 (0a000007) ------------------------------------------------------- Member: (0a000007) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: String TypeRef #9 (01000009) ------------------------------------------------------- Token: 0x01000009 ResolutionScope: 0x23000001 TypeRefName: System.Reflection.AssemblyTrademarkAttribute MemberRef #1 (0a000008) ------------------------------------------------------- Member: (0a000008) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: String TypeRef #10 (0100000a) ------------------------------------------------------- Token: 0x0100000a ResolutionScope: 0x23000001 TypeRefName: System.Reflection.AssemblyCultureAttribute MemberRef #1 (0a000009) ------------------------------------------------------- Member: (0a000009) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: String TypeRef #11 (0100000b) ------------------------------------------------------- Token: 0x0100000b ResolutionScope: 0x23000001 TypeRefName: System.Runtime.InteropServices.ComVisibleAttribute MemberRef #1 (0a00000a) ------------------------------------------------------- Member: (0a00000a) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: Boolean TypeRef #12 (0100000c) ------------------------------------------------------- Token: 0x0100000c ResolutionScope: 0x23000001 TypeRefName: System.Runtime.InteropServices.GuidAttribute MemberRef #1 (0a00000b) ------------------------------------------------------- Member: (0a00000b) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: String TypeRef #13 (0100000d) ------------------------------------------------------- Token: 0x0100000d ResolutionScope: 0x23000001 TypeRefName: System.Reflection.AssemblyVersionAttribute MemberRef #1 (0a00000c) ------------------------------------------------------- Member: (0a00000c) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: String TypeRef #14 (0100000e) ------------------------------------------------------- Token: 0x0100000e ResolutionScope: 0x23000001 TypeRefName: System.Reflection.AssemblyFileVersionAttribute MemberRef #1 (0a00000d) ------------------------------------------------------- Member: (0a00000d) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: String TypeRef #15 (0100000f) ------------------------------------------------------- Token: 0x0100000f ResolutionScope: 0x23000001 TypeRefName: System.Diagnostics.DebuggableAttribute MemberRef #1 (0a00000e) ------------------------------------------------------- Member: (0a00000e) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: ValueClass DebuggingModes TypeRef #16 (01000010) ------------------------------------------------------- Token: 0x01000010 ResolutionScope: 0x0100000f TypeRefName: DebuggingModes TypeRef #17 (01000011) ------------------------------------------------------- Token: 0x01000011 ResolutionScope: 0x23000001 TypeRefName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute MemberRef #1 (0a00000f) ------------------------------------------------------- Member: (0a00000f) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 1 Arguments Argument #1: I4 TypeRef #18 (01000012) ------------------------------------------------------- Token: 0x01000012 ResolutionScope: 0x23000001 TypeRefName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute MemberRef #1 (0a000010) ------------------------------------------------------- Member: (0a000010) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #19 (01000013) ------------------------------------------------------- Token: 0x01000013 ResolutionScope: 0x23000001 TypeRefName: System.Console MemberRef #1 (0a000011) ------------------------------------------------------- Member: (0a000011) WriteLine: CallCnvntn: [DEFAULT] ReturnType: Void 1 Arguments Argument #1: String Assembly ------------------------------------------------------- Token: 0x20000001 Name : Program Public Key : Hash Algorithm : 0x00008004 Version: 1.0.0.0 Major Version: 0x00000001 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale:Flags : [none] ( 00000000) CustomAttribute #1 (0c000001) ------------------------------------------------------- CustomAttribute Type: 0a000001 CustomAttributeName: System.Runtime.Versioning.TargetFrameworkAttribute :: instance void .ctor(class System.String) Length: 77 Value : 01 00 1c 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 35 2e 31 01 >,Version=v4.5.1 < : 00 54 0e 14 46 72 61 6d 65 77 6f 72 6b 44 69 73 > T FrameworkDis< : 70 6c 61 79 4e 61 6d 65 14 2e 4e 45 54 20 46 72 >playName .NET Fr< : 61 6d 65 77 6f 72 6b 20 34 2e 35 2e 31 >amework 4.5.1 < ctor args: (".NETFramework,Version=v4.5.1") CustomAttribute #2 (0c000002) ------------------------------------------------------- CustomAttribute Type: 0a000002 CustomAttributeName: System.Reflection.AssemblyTitleAttribute :: instance void .ctor(class System.String) Length: 12 Value : 01 00 07 50 72 6f 67 72 61 6d 00 00 > Program < ctor args: ("Program") CustomAttribute #3 (0c000003) ------------------------------------------------------- CustomAttribute Type: 0a000003 CustomAttributeName: System.Reflection.AssemblyDescriptionAttribute :: instance void .ctor(class System.String) Length: 5 Value : 01 00 00 00 00 > < ctor args: ("") CustomAttribute #4 (0c000004) ------------------------------------------------------- CustomAttribute Type: 0a000004 CustomAttributeName: System.Reflection.AssemblyConfigurationAttribute :: instance void .ctor(class System.String) Length: 5 Value : 01 00 00 00 00 > < ctor args: ("") CustomAttribute #5 (0c000005) ------------------------------------------------------- CustomAttribute Type: 0a000005 CustomAttributeName: System.Reflection.AssemblyCompanyAttribute :: instance void .ctor(class System.String) Length: 5 Value : 01 00 00 00 00 > < ctor args: ("") CustomAttribute #6 (0c000006) ------------------------------------------------------- CustomAttribute Type: 0a000006 CustomAttributeName: System.Reflection.AssemblyProductAttribute :: instance void .ctor(class System.String) Length: 12 Value : 01 00 07 50 72 6f 67 72 61 6d 00 00 > Program < ctor args: ("Program") CustomAttribute #7 (0c000007) ------------------------------------------------------- CustomAttribute Type: 0a000007 CustomAttributeName: System.Reflection.AssemblyCopyrightAttribute :: instance void .ctor(class System.String) Length: 23 Value : 01 00 12 43 6f 70 79 72 69 67 68 74 20 c2 a9 20 > Copyright < : 20 32 30 31 35 00 00 > 2015 < ctor args: ("Copyright © 2015") CustomAttribute #8 (0c000008) ------------------------------------------------------- CustomAttribute Type: 0a000008 CustomAttributeName: System.Reflection.AssemblyTrademarkAttribute :: instance void .ctor(class System.String) Length: 5 Value : 01 00 00 00 00 > < ctor args: ("") CustomAttribute #9 (0c000009) ------------------------------------------------------- CustomAttribute Type: 0a00000a CustomAttributeName: System.Runtime.InteropServices.ComVisibleAttribute :: instance void .ctor(bool) Length: 5 Value : 01 00 00 00 00 > < ctor args: (not decode> ) CustomAttribute #10 (0c00000a) ------------------------------------------------------- CustomAttribute Type: 0a00000b CustomAttributeName: System.Runtime.InteropServices.GuidAttribute :: instance void .ctor(class System.String) Length: 41 Value : 01 00 24 63 34 66 35 35 64 66 63 2d 63 38 63 66 > $c4f55dfc-c8cf< : 2d 34 66 64 37 2d 39 65 63 37 2d 37 62 63 35 37 >-4fd7-9ec7-7bc57< : 31 64 65 65 65 35 63 00 00 >1deee5c < ctor args: ("c4f55dfc-c8cf-4fd7-9ec7-7bc571deee5c") CustomAttribute #11 (0c00000b) ------------------------------------------------------- CustomAttribute Type: 0a00000d CustomAttributeName: System.Reflection.AssemblyFileVersionAttribute :: instance void .ctor(class System.String) Length: 12 Value : 01 00 07 31 2e 30 2e 30 2e 30 00 00 > 1.0.0.0 < ctor args: ("1.0.0.0") CustomAttribute #12 (0c00000c) ------------------------------------------------------- CustomAttribute Type: 0a00000e CustomAttributeName: System.Diagnostics.DebuggableAttribute :: instance void .ctor(value class DebuggingModes) Length: 8 Value : 01 00 07 01 00 00 00 00 > < ctor args: ( not decode> ) CustomAttribute #13 (0c00000d) ------------------------------------------------------- CustomAttribute Type: 0a00000f CustomAttributeName: System.Runtime.CompilerServices.CompilationRelaxationsAttribute :: instance void .ctor(int32) Length: 8 Value : 01 00 08 00 00 00 00 00 > < ctor args: (8) CustomAttribute #14 (0c00000e) ------------------------------------------------------- CustomAttribute Type: 0a000010 CustomAttributeName: System.Runtime.CompilerServices.RuntimeCompatibilityAttribute :: instance void .ctor() Length: 30 Value : 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 < ctor args: () 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: HashValue Blob: Flags: [none] (00000000) User Strings ------------------------------------------------------- 70000001 : ( 2) L"Hi" Coff symbol name overhead: 0 =========================================================== =========================================================== ===========================================================
幸运的是,ILDasm在合适的地方处理元数据表和联合信息所以你不必转换表单行信息。例如,在之前提过的转储,你看当ILDasm显示一个TypeDef条目,在第一个TypeRef条目显示之前,对应的成员定义信息和它一起显示。
你不需要全部理解你看到的任何事情。重要的是记住Program.exe包含一个名字叫做Program的TypeDef。这个类型识别一个从System.Object(一个被其它程序集引用的类型)继承的公共密封类。Program类型也定义两个方法:Main和.ctor(一个构造器)。
Main是一个公共的、静态的方法它的代码是IL(相对于本地CPU代码,比如x86)。Main没有返回类型也没有参数。构造器方法(总是以.ctor的名字出现)是公共的,它的代码也是IL。构造器没有返回类型,没有参数,有一个this指针,当方法被调用时会构造一个对象的内存,this指针会指向它。
我强烈支持你体验ILDasm。它会为你展示大量的信息,你看到的你会理解的更多,你将更理解CLR和它的能力。正如你将看到的,我在这本书中使用ILDasm相当多。
只是为了有趣,让我们看一些关于Program.exe程序集的统计。当你选择ILDasm的View/Statistics菜单项,会显示以下信息。
File size : 4608 PE header size : 512 (496 used) (11.11%) PE additional info : 1535 (33.31%) Num.of PE sections : 3 CLR header size : 72 ( 1.56%) CLR meta-data size : 1508 (32.73%) CLR additional info : 0 ( 0.00%) CLR method headers : 2 ( 0.04%) Managed code : 20 ( 0.43%) Data : 2048 (44.44%) Unaccounted : -1089 (-23.63%) Num.of PE sections : 3 .text - 2048 .rsrc - 1536 .reloc - 512 CLR meta-data size : 1508 Module - 1 (10 bytes) TypeDef - 2 (28 bytes) 0 interfaces, 0 explicit layout TypeRef - 19 (114 bytes) MethodDef - 2 (28 bytes) 0 abstract, 0 native, 2 bodies MemberRef - 18 (108 bytes) CustomAttribute- 14 (84 bytes) Assembly - 1 (22 bytes) AssemblyRef - 1 (20 bytes) Strings - 630 bytes Blobs - 272 bytes UserStrings - 8 bytes Guids - 16 bytes Uncategorized - 168 bytes CLR method headers : 2 Num.of method bodies - 2 Num.of fat headers - 0 Num.of tiny headers - 2 Managed code : 20 Ave method size - 10
这里你可以看到文件大小(以字节为单位)和组成文件的各部分大小(以字节和百分比为单位)。从这个很小的Program.cs应用程序可以看出,文件中大部分是PE头文件和元数据。事实上,IL代码仅仅占用了20字节。当然,随着应用程序增大,它将重用它的大多数类型和引用其它类型和程序集,当和整体的文件大小相比的时候元文件和头文件信息缩减了。
注意 顺便说一下,ILDasm.exe有一个影响文件大小的漏洞。特别的,你不能信任未包含在数目中的信息。 |
Combining Modules to Form an Assembly(组合模块形成一个程序集)
在之前章节讨论过的Program.exe文件不止是带有元数据的PE文件;它也是一个程序集。一个程序集是一个或多个文件包含类型定义、资源文件的一个集合。其中的一个程序集文件选择持有一个载货单。载货单是另一个元数据表格的集合主要包含程序集的一部分文件的名字。它们也描述程序集的版本,区域,发布者,公共导出类型,和组成程序集的所有文件。
CLR在程序集集合上工作;CLR总是先加载包含载货单元数据表格的文件然后使用载货单获取在程序集中的其它文件的名字。这里是一些你应该记住的程序集集合特征:
*一个程序集定义可重用类型。
*一个程序集通过版本号标记。
*一个程序集可以有与之相关的安全信息。
一个程序集的特有文件没有这些属性——期望文件包含载货单元数据表格。
程序集,版本,安全和使用类型,你必须把它们放在模块中因为它们是程序集的一部分。在大多数案例中,一个程序集由一个单独的文件组成,就像之前的Program.exe例子一样。然而,一个程序集也可以由多个文件组成:一些带有元数据的PE文件和一些资源文件比如:.gif或.jpg文件。它可以帮助你考虑一个程序集是合理的EXE还是一个DLL。
我确定你们大多数人读到这都很疑惑为什么微软引进程序集概念。原因是一个程序集允许你从逻辑和物理的概念上解耦可重用类型。例如,一个程序集可以又几个类型组成。你可以把常用的类型方法放到一个文件中不常用的放到另一个文件中。假如你的程序集是通过Internet下载部署,不常用类型可能永远也不会下载到客户端如果客户端从不访问那个类型。例如,一个独立的软件供应商(independent software vendor,ISV)专攻UI控件可能选择在一个分离的模块(满足微软的商标要求)中实现Active Accessibility类型。只有需要额外访问性特征的用户才需要下载这个模块。
在应用程序的配置文件中通过指定codeBase元素(在第三章讨论)你可以配置一个应用程序下载程序集文件。codeBase元素标识一个URL指向可以找到一个程序集所有文件的地方。当尝试下载一个程序集的文件,CLR获取codeBase的元素的URL然后检查机器的下载缓存文件是不是已经有了。如果是,文件会被加载。如果文件不再缓存中,CLR从URL指向的位置下载文件到缓存。假如文件找不到,CLR在运行时抛出一个FileNotFoundException异常。
我指出三个使用多程序集的原因:
*在分隔的文件之间直接分配你的多个类型,允许文件逐步下载就像Internet下载场景描述的那样。把多个类型分配到隔离的文件同时允许为你的应用程序部分或逐步打包和部署。
*你可以添加资源或者数据文件到你的程序集。例如,你可以有一个类型用于计算一些保险信息。这个类型可能需要访问一些保险精算的表格来做计算。代替把保险精算表格嵌入到你的源代码,你可以使用一个工具(比如程序集链接者,AL.exe,稍后讨论)使数据文件被认为是程序集的一部分。顺便提一下,着这个数据文件可以是任何格式的——一个文本文件,一个微软Excel电子表格,一个微软Word表,或者你喜欢的无论什么——只要你的应用程序知道怎么转换文件的内容。
*你可以创建由不同语言实现的类型组成的程序集。例如,你可以实现一些C#的类型,一些微软Visual Basic的类型,和一些由其它语言实现的。当你编译C#源码类型编译器生成一个分离的模块。接下来你可以使用一个工具把所有的模块组合成一个程序集。对于使用程序集的开发者,程序集呈现的只是一堆类型;开发者甚至不知道程序集使用了不同的语言开发。顺便提一下,假如你喜欢,你可以运行ILDasm.exe并把它全部转为IL源码文件。ILAsm.exe将会产生一个包含所有类型的文件。这个技术要求你的源码编译器只生成IL代码。