程序集(Assembly)和模块(Managed Module)

时间:2024-04-03 12:37:50

前言

一直都用集成开发坏境(IDE),一直对模块和程序集的概念理解的不是很直观,因为一Build就把你的单个模块塞进程序集里面去了。当然,对你的编程也不会造成太大的影响。但有些东西你最好还是知道比较好,而且对安装部署你的应用程序一定条件下有帮助。

首先我们先来认识下托管模块(Managed Module)。PE头+CLR头+元数据metadata+IL。想必关于这个结构应该不会很陌生。还是费点笔墨介绍一下:

1, PE头,这是标准的window PE头吧,顺序结构的,是否是32位的,是CUI还是GUI的,还是library的等信息。刚写完一段代码利用这里的信息来编码判断文件是基于32还是64的。

2, CLR头呢,自然是CLR要用的,里面有CLR的版本信息,若是可执行程序的话,入口函数main位置信息,还包含了下面将要提到的metadata的位置信息等。

3, Metadata,元数据。这是一个很重要的概念,使得咱们的托管模块有自我描述的能力。这个地方包含的都是咱这个模块里面的元素,类型,方法,变量等,另外如果还有引用别的模块的类似这些信息。这是区别于非托管模块的一个重要点:

  1. Dev10(微软内部的说法,指visual studio 2010)的编码智能提示,你输入一个点或者ctrl+j快捷方式,就能列出所有可用的类型和成员来。该功能就依赖于此;
  2. 咱有对象的序列化和反序列化(XML或者Binary的格式)也需要依赖于元数据对对象结构的描述。WCF的数据契约DataContract,WWF的工作流状态持久化机制,很多。
  3. 元数据里面存了对别的对象的引用,所以可以在运行时再加载需要的对象(通过JIT编译),这使得我们不需要再像其他非托管模块编译的时候需要把其他被引用模块一起编译进来。
  4. 咱重要的垃圾回收机制(GC)的实现也依赖于此,想必大伙都对这机制不会陌生,说是当一个对象没有被再引用的时候会被标志城可回收的,就像清洁工阿姨判断这个物品时别人丢弃的还是暂时存放在地上的,然后清洁工阿姨会根据自己的判断来清理这些东西。但是当一个对象对进行回收处理的时候,该对象引用的其他需要被回收的对象abc也需要被回收掉,这个时候就可以通过咱的元数据去定位abc了。
  5. 等等

4, IL, intermediate language,是介于高级编程语言和计算机指令的中间态指令。如何产生?语言编译器产生,用C#得都熟悉CSC.exe命令,该命令可以把你写的helloword程序转化成IL。如何执行?这就有依赖于前面提到过的JIT编译器了。简单的一句话表达:当代码块别第一次执行的时候会执行实时编译,然后把metadata指向的该方法内存地址替换掉,然后再执行该方法内存空间上的指令,所以第二次执行的就不需要编译了。写到这里,我想到了两个问题,一个是性能问题,另外一个是编译粒度问题。不妨简单的来讨论一下:

性能问题。

把IL编译成计算机指令,当然会有性能消耗,这个好像也是非托管和托管之间争议的最大的地方吧,非托管就直接编译成计算机指令了,就能直接执行了。托管的还需要再被编译一次,虽然这个过程作了优化,也相信微软一直会不断优化这个过程,总归有性能方面的担心,很正常。这种差异肯定是客观存在的,不过下面的一些介绍会些许减少一些你的顾虑吧:

i.      在介绍时候被提到了就是一个代码块只会被编译一次,第二次就直接使用计算机指令了。看到这个你是不是有了一些想法,对的编程是不是起到了一定的作用?想说的是合理的代码结构设计和编程习惯,会让你的代码变快。还不明白的话就举个具体例子,当你要实现像个类似功能,他们80%的逻辑是一样的,试着把公共模块抽离出来做成一个可能只有参数差异的方法供这些功能调用,不管是从维护还是从性能的角度您都提高了你的性能。(当然事情并非那么绝对。为了不误导各位,这里不得不提的是方法内联的编译优化概念。具体的请自己查阅或关注近期更新的博客,篇幅太长不适合阅读)

ii.      第二个想说的是我们可以充分JIT的智能优化,最直观的是最CPU指令的优化,不同类型CPU对处理同一个动作有不同的适合自己的最优化指令,JIT可以做到这个。为什么单独拿出来说这个,因为这个是相对于静态非托管编译的一个很大的优化。哦,你懂了。

iii.      当然你如果还是心存芥蒂的话,其实没关系,在SDK目录下面有一个命令教NGen.exe,对了,可以把IL直接转化成计算机指令,关注以后的博客更新会有提到如何使用以及注意事项。

编译的粒度问题。

对于动态编译执行的代码,选择在何时编译,编译多少相关的代码块,这种时机和粒度的选择是于性能和计算机资源利用效率是至关重要的。(写着写着又篇幅过长了,打算留到后面再作介绍)

认识完托管模块的结构后,再来看看程序集Assembly。

看到这个集,就知道是个集合,程序集,就是很多程序的集合,那程序文件是什么,就是上面我们提到的模块,当然能被称作程序文件的不只是模块啦,还有资源文件类的,图标,本地化语言包等。所以啊,一个或者多个程序文件就构成了我们的程序集。放心,这里介绍的程序集的概念不会太多打破你之前的认识。只是再稍作介绍而已。

当我们用命令行得到一个test.exe的时候,这就是一个程序集,只是是一个单个模块没有资源文件的程序集。

Csc /out:test.exe /t:exe program.cs

这里你想到怎么让一个程序集能有多个模块呢,又怎么能够有包含资源文件呢?咱接着输入命令行:

Csc  /t:module testmodule.cs

你就会得到一个testmodule.netmodule的文件,没见过吧,这就是一个模块文件,你可以同样的方式得到另外一个模块文件然后在用AL.exe把两个模块文件编译进一个程序集mergedAssemly.dll:

Al /out:mergedAssemly.dll /t:library testmodule.netmodule testmodule1.netmodule

这里有几点注意的是:

  • al.exe /t的类型只能是library,想啊,如果是exe或者winexe的话,你都没指定入口函数呢(你也可以用al.exe指定入口函数的用法)。
  • 这里程序集的执行还是依赖于物理的.netmodule文件的,并没有把模块文件内嵌到程序集里面去,这给你了条件可以按需部署你的程序了。
  • Assembly 的metadata里面专门有一个是manifest的区域空间,清单嘛,想想应该也知道是用来包含该程序集里面的程序文件的信息的。
  • 当然csc.exe和al.exe还能做很多事情,不一一举例了,为什么单独build一个模块文件的目的也在于更直观的让大伙体会到两者的关系。
  • 对于CLR来说,是不能直接使用Module的。Assembly是一个重用的单元,有自己的版本和安全信息的保护。