CLR via C# (二)

本章导读

1.将类型生成到模块中

2.元数据描述

3.将模块合并成程序集

4.程序集版本资源信息

前言:

长久以来,windows应用程序一直因为不稳定和过于复杂而口碑不佳;究其原因:

  首先:所有的应用程序都要使用来自windows和其他厂商的DLL,这样一来,由于各个厂商之间类型的更新升级,很容易造成“向后兼容”的问题;很多人会发现,当安装一个新应用时,他可能会莫名其妙的破坏另一个已经安装好的程序,这个问题称作:“DLL hell”;

  第二:应用程序安装的复杂性,如今,大多数应用程序在安装时都会影响到系统的全部组建(比如:文件复制到多个目录,更改注册表等等);

  第三:应用程序在安装时会涉及安全性;

  (可是这些问题什么时候能够解决呢???)

.net framework 正在尝试彻底解决DLl hell问题,以及安全性问题,不过看起来还有很长的路要走......

将类型生成到模块中

通常来说,.net开发中,我们写的代码可以直接通过[宇宙第一IDE:Visual Studio]生成dll或是exe文件,很少关心VS是怎么做到的,下面咱就简单说一说从源代码到一个可部署文件的另外一种方法:直接使用CSC.exe来编译我们的源代码;

假如有如下代码:

public class Method
{
    public static void Print(System.String message)
    {
         System.Console.WriteLine(message);
    }
}

public class MyClass
{
    public static void Main()
    {
        Method.Print("-:你好,我来自Method类型!");
        System.Console.WriteLine("-:你好,我来自MyClass类型!");
    }
}

这两个类分别放置于Method.cs和MyClass.cs文件中,现在,打开Visual Studio自带的命令行提示工具,定位到cs文件所在文件夹,然后执行如下命令:

csc.exe /out:myclass.exe /t:exe /r:MSCorLib.dll Method.cs MyClass.cs

这个命令指示C#编译器生成一个名为myclass.exe的可执行文件(/out:myclass.exe),生成的文件属于Win32控制台应用程序类型(/t:exe);注意,在代码中,我们使用System.Console这个类型的WriteLine方法,这个类型存在于MSCorLib.dll,所以,我们要添加对该程序集的引用:/r:MSCorLib.dll;

此外,由于/out:myclass.exe和/t:exe开关是C#编译器默认设定的,而且编译器会自动引用MSCorLib.dll,所以命令可以简化成:

csc.exe Method.cs MyClass.cs

windows支持两种类型的应用程序:具有控制台用户界面(CUI)的和具有图形用户界面(GUI)的,对应的/t:exe为CUI,/t:winexe为GUI;

此外,我们再介绍一个特别的文件:响应文件(response file),它就是一个文本文件,不过扩展名为(.rsp),我们可以把要在命令行添加的各种开关写到这个文件中,然后在命令行中引用这个文件:

/out:myclass.exe
/target:exe

然后将这个文件存为:myresponse.rsp(名字可以自定义),在命令行中这样写:

csc.exe @myresponse.rsp Method.cs MyClass.cs

C#编译器允许同时指定多个响应文件,其实,编译器会自动查找两个名为CSC.rsp的文件:先是在当前目录查找本地CSC.rsp文件,然后在csc.exe所在的目录查找全局CSC.rsp文件,在这个全局文件中会引用多个程序集(具体自己可以打开来看);使用/noconfig命令开关可以告诉编译器忽略局部和全局CSC.rsp文件;

元数据描述

通过制定/t开关,我们可以知道自己创建的是什么类型的PE文件,但是myclass.exe文件中到底包含什么内容呢?由上一篇文章可知,一个托管PE文件由4个部分构成:

  1.PE(+)头:这个是windows要求的标准信息;

  2.CLR头:包含模块在生成时所面向的CLR的major和minor版本号,一些标志(flag),一个MethodDef token,(如果是CUI或是GUI程序,还包括入口方法),以及一 个可选的强名称数字签名(可以查看CorHdr.h头文件中定义的IMAGE_COR20_HEADER,了解CLR头);

  3.元数据:是一个二进制的数据块,它是一组数据表的集合,这些表分为三种---1.定义表(definition table)2.引用表(reference table)3.清单表(manifest table);

  4.IL;

下面我们简单的介绍一些主要的表:

  常用的元数据定义表有:

表名称 说明
ModuleDef 总是包含一个用于标识模块的记录项,在这个记录项中主要包含:模块的文件名和扩展名(不含路径),模块的版本ID(GUID形式,由编译器创建)
TypeDef 模块中定义的每个类型都在这个定义表中有一个对应的记录项,每个记录项主要包含:名称,基类型,标志(public,private等),索引(这些索引指向该类型在MethodDef表中定义的方法,在FieldDef中定义的字段,在PropertyDef中定义的属性以及在EventDef中定义的事件)
MethodDef 模块中定义的每个方法都在这个定义表中有一个记录项,这个记录项主要包含:名称,标志(public,private,virtual,abstract,static,final等),签名以及该方法的IL代码在模块中的偏移量,还包括对ParamDef表中的一个记录项的引用,这个表中包含与方法参数有关的信息
FieldDef 模块中定义的每个字段在这个表中都有一个对应的记录项,这个记录项包含:标志(public,private等),类型和名称
ParamDef 模块中定义的每个参数都在这个表中有一个相应的记录项,这个记录项包含:标志(in,out,retval等),类型和名称
PropertyDef 模块中定义的每个属性都在这个表中有一个相应的记录项,这个记录项包含:标志,类型和名称
EventDef 模块中定义的每个事件都在这个表中有一个相应的记录项,这个记录项包含:标志和名称

编译器在编译源代码时,代码定义的任何一样东西都会导致在上述表中的某个表创建一个记录项;编译器还会检测源代码中引用的类型,字段,方法,属性和事件,并在以下表中的某个表创建相应的记录项;

  常用的元数据引用表有:

表名称 说明
AssemblyRef 模块中引用的每个程序集在这个表中都有一个对应的记录项,每个记录项包含的信息有:程序集名称(不含路径和扩展名),版本号,语言文化以及公钥标记(一个根据发布者的公钥生成的小哈希值,标记了程序集的发布者),还包含有标志(flag)和一个哈希值
ModuleRef 当前模块引用的类型是由别的PE模块实现的,所有的这些模块都会在这个表中有一个记录项,这个记录项包含:文件名和扩展名(不含路径)
TypeRef 模块引用的每个类型在这个表中都有一个记录项,这个记录项包含:名称和一个引用(指向类型的位置:如果类型是在另外一个类型中实现的,则引用指向的是TypeRef记录;如果类型在同一个模块中实现,引用指向的就是一个ModuleDef记录项,如果类型是在调用程序集内部的另外一个模块中实现的,引用指向的就是一个ModuleRef记录项,如果类型是在一个不同的程序集中实现的,引用指向的就是一个AssemblyRef记录项)
MemberRef 模块中引用的每个成员(字段,方法,属性和事件方法)都在这个表中有一个记录项,这个记录项包含:名称和签名,并指向对成员进行定义的那个类型的TypeRef记录项

为了查看元数据的信息,我们可以使用IL反汇编器:ILDasm.exe;在VisualStudio的命令行中执行:

ILDasm myclass.exe

就可以查看myclass.exe这个程序集的元数据信息了,具体的使用方法就自行google吧!!!

 将模块合并成程序集

之前简单的介绍过程序集,再来复习一下:

程序集是进行重用,版本控制和应用安全性设置的一个基本单元;使用程序集可以区分可重用类型的逻辑表示和物理表示;程序集是一个或多个类型定义文件以及资源文件的集合,在这些文件中,有一个文件容纳了清单数据,清单也是一组数据表的集合,常用的清单数据表有:

清单元数据表名称 说明
AssemblyDef 如果该模块标记的是一个程序集,就在这个元数据表中包含一个记录项,这个记录项包含:程序集名称(不含路径和扩展名),版本,语言文化,标志,哈希算法以及发布者公钥
FileDef 作为程序集一部分的每个PE文件和资源文件在这个表中都有一个记录项,其中包含:文件名,扩展名(不含路径),哈希值,标志
ManifestResourceDef 作为程序集一部分的每个资源文件在这个表中都有一个对应的记录项,其中包含:名称,标志,FileDef表的一个索引(指出资源包含在哪个文件中)
ExportedTypesDef 从程序集的所有PE模块中导出的每个public类型在这个表中都由一个记录项,其中包含:名称,FileDef表的一个索引以及TypeDef表的一个索引

包含清单的程序集文件还有一个AssemblyRef表,程序集的所有文件引用的所有程序集在这个表中都有一个对应的记录项;

程序集是一个抽象的概念,是一个或多个模块文件和资源文件组成的逻辑单元,其中必然包含且只有一个后缀为.exe或者.dll的主模块文件;

CLR操作的是程序集,所以CLR总是首先加载包含“清单元数据”的那个文件。

大多数时候,程序集只由一个文件构成,但是也可以由多个文件构成(一些是含有元数据的PE文件,一些是.gif或.jpg这样的资源文件),而且,VisualStudio不能创建多文件程序集(好悲剧.....),使用多文件程序集有以下好处:

  1.可用单独的文件对类型进行划分,允许文件以增量的方式下载;

  2.可在自己的程序集中添加资源或是数据文件;

  3.程序集包含的各个类型可用不同的语言来实现;

注意:假如多个类型能共享相同的版本号和安全性设置,那么出于性能的考虑,建议将这些类型放到一个文件中;

客户端代码执行时会调用方法,一个方法被首次调用时,CLR会检测作为参数,返回值或是局部变量而被方法引用的类型,然后,CLR尝试加载所引用的程序集中包含了清单的那个文件,如果要访问的类型在这个文件中,CLR会执行内部登记工作,允许使用这个类型;如果不在,CLR会尝试加载需要的文件,然后同样执行内部登记工作,允许使用这个类型;

为了生成一个新的程序集,所有引用的程序集的所有文件都必须存在;但是,为了让一个程序运行起来,并不要求被引用的程序集的所有文件都存在;

接下来咱们玩点有意思的,还用之前定义的两个类:Method.cs和MyClass.cs

我们先用C#编译器:

 

> csc /t:library MyClass.cs

> csc /t:library /r:MyClass.dll MyClass.cs

 

这样能生成一个MyClass.dll,如果换成/t:exe就能生成MyClass.exe;在这里要注意的是:一定要加上

/r:MyClass.dll

 

因为在MyClass.cs中引用了Method.cs中的方法,所以在单独编译MyClass.cs的时候一定要添加对Method.cs(或是Method.netmodule)的引用;

接下来我们用另外一种工具:AL.exe(程序集链接器);

用AL.exe可以生成只含资源文件的程序集(也称为附属程序集),这种程序集在本地化的时候非常有用,现在执行如下命令:

 

> csc /t:module Method.cs
> csc /t:module /addmodule:Method.netmodule MyClass.cs
> al /out:MyClass.dll /t:library Method.netmodule MyClass.netmodule

 

通过上面的命令,我们同样生成了MyClass.dll,不同的是我们这次用的是AL.exe,这是第一点不同,第二点不同之处在于:这个dll是由三个文件构成的,MyClass.dll,Method.netmodule和MyClass.netmodule;AL.exe不能将多个文件合并成一个文件;

通过C#编译器和AL.exe都能够为程序集添加资源文件,执行如下命令:

> al /out:MyClass.exe /t:exe /main:MyClass.Main /win32icon:F:\temp\MyClass.ico MyClass.netmodule

通过这个命令,我们生成了一个MyClass.exe执行程序,而且,我们还为这个执行程序分配了一个新的图标;

csc.exe和al.exe这两个程序非常有用,通过它们我们可以了解到程序编译的一些底层知识,如果可能的话,我们最好都试着用一下这个工具!

 程序集版本资源信息

使用csc.exe或是al.exe生成一个PE文件程序集时,还会在PE文件中嵌入一个标准的win32版本资源,我们在代码中通过System.Diagnostics.FileVersionInfo.GetVersionInfo方法可以查看这些信息;在生成程序集的时候,我们可以在assembly级别应用定制attribute来定义这些信息,如果使用al.exe来生成程序集,还可以使用一些命令开关来指定这些信息;

程序集版本号都具有相同的格式:每个都由4个句点分隔的部分构成,比如:

2.5.719.2,从前往后依次代表:主版本号,次版本号,内部版本号,修订号;

一个程序集有三个版本号与之关联:

  1.AssemblyFileVersion:存储在Win32版本资源中,仅供参考;

  2.AssemblyInformationalVersion:存储在Win32版本资源中,仅供参考;

  3.AssemblyVersion:存储在AssemblyDef清单元数据表中,唯一的标识了一个程序集,CLR在绑定强命名程序集时,使用这个版本号;

《未完待续》

 

posted @ 2014-10-31 15:18  寒江鸟  阅读(229)  评论(0编辑  收藏  举报