|
最近看到不少同学在咨询.Net或者C#的基础问题,这里做一个统一的整理。通过从底层往上层讲解的方式总结一下。
其实Microsoft 已经在2012年把整套设计理念,实现流程标准化了,对应的标准分别是ECMA 335/334 其中前者对应整个的CLI(Common Language Infrastructure)后者对应C#语言的实现标准。
微软的.NET Framework 介绍了许多的概念,技术,以及术语。本文主要介绍.NET 以及托管代码是如何执行的。
如果你想使用.NET Framework,则必然会选择一门对应的语言进行开发,而不同的语言往往具备不同的目标用途和对应的能力,像使用非托管代码C/C++,能进行非常底层的操作系统编程。可以根据需要进行内存管理,创建线程等等,而使用VB则可以非常方便地开发UI相关的应用程序。
什么是CLR
CLR(Common Language Runtime) 指的是能够被各种不同编程语言使用的运行时库,是一种通用能力的抽象。其核心特点是类似内存管理,程序集加载,安全,异常处理,或者多线程同步等基础功能模块能够被所有实现这个规范的编程语言使用。
这里做下解释,公共语言运行时库是不知道用户会使用哪种语言进行具体的编码,需要用户自行进行选择,只要是能方便地实现需求的,开发者可以针对CLR开发对应的编程语言。
因此从这个意义上讲,不同的编程语言将会具备不同的优势,像APL语法适合数学和金融类应用开发,相对于Perl 能够节省大量的编程时间。微软正是基于此类设想,在CLR的基础上开发了很多的编程语言,像 C++/CLI, C# , Visual Basic, F#, Iron Python, Iron Ruby, 以及 Intermediate Language (IL) Assembler。当然也有很多大学,公司,以及其他机构创建了对应的编译器,在CLR的基础上创建了很多专用的程序语言。目前已知的针对CLR的编译器开发者有 Ada, APL, Caml, COBOL, Eiffel, Forth, Fortran, Haskell, Lexico, LISP, LOGO, Lua, Mercury, ML, Mondrian, Oberon, Pascal, Perl, PHP, Prolog, RPG, Scheme, Smalltalk, and Tcl/Tk 种类非常繁多。
托管(Managed)语言和非托管语言的差异
总之,人们可以创建各种支持在CLR上运行的编程语言,然后利用相应的编译程序去做语法检查并且分析源文件,不管何种编译器,最终的结果是编译成 托管代码模块,这是什么意思呢?托管代码指的是标准的32位或者64位PE32、PE32+(Portable excutable) 可执行文件,就是咱们常见的exe 文件(注意这个跟咱们常用的exe文件示有差别的),但是这些exe文件需要依赖CLR去运行。托管的程序库总是利用DEP(Data Execution Prevention)以及ASLR(Address Space Layout Randomization)这两个特性在Windows系统上去提高运行的安全性。具体的编译流程示例如下:

编译源程序代码到托管代码的过程示例
托管代码一般有如下构成(这个不同于COFF文件的exe 的差别也在这里,托管的exe包含了Metadata和IL 中间代码,在第一次启动的时候会进行对应的编译):

托管代码的构成
与托管代码(Managed Code)对应的是Native Code,Native Code 是指编译到对应平台的指令,像x86,x64以及ARM指令集组成的本地硬件可以直接执行的指令集代码。而托管代码则是由CLR 兼容的编译器统一生成的IL 中间代码。在具体的平台第一次运行的时候,才会由CLR底层对应的库像MSCorEE.dll翻译成对应的平台相关的Native Code,这个过程就是JIT(Just In Time)过程。以上的表格中的Metadata,ILcode 是托管程序有别于非托管程序的中要区别(一般非托管程序库中是没有这类信息的),其中在CLR环境中Metadata元数据主要有以下几个用途:
1.元数据记录了原始程序源代码和中间代码之间的映射关系,用户不需要关注头文件等信息。
2.Visual Studio 使用Metadata 来辅助编写代码逻辑,像大家熟知的Intellisense 会解析元数据来辅助显示当前的类型中存在哪些方法,属性,事件,以及成员变量。
3.CLR验证程序会使用元数据来验证代码执行的类型安全性,如进行类型安全性检查,参数匹配检查等。
4.另外元数据也是序列化的实现基础,能够将对象中的所有成员序列化到内存,从而完成类型远程传输,保存。
5.Metadata 是GC (Garbage Collector)追踪各种不同对象的生命周期,管理各种对象内存生命周期的关键连接数据。
这里需要重点说明一下,C#禁止类似于C++的多继承,并且所有对象都是继承于System.object,这个为所有的类型提供了判定是否是同一个对象,获取对应的Hash值等提供了通用类型能力。
托管语言又是如何编译成对应的Assembly(程序集)的呢?
CLR 并不是以模块(Module)为单位执行代码逻辑的,而是以程序集(Assemblies)的方式运行的。这个程序集的概念还是非常抽象的,首先是因为程序集只是一个或者多个模块或者源文件的逻辑分组,另外,一个程序集是一个最小的重用,安全,版本部署的集合单元。具体的构成如下:

组合托管模块到程序集
CLR 到底是什么?
每个托管的程序集最终需要放到对应的可执行应用中执行。因为CLR是负责管理程序集中可执行的代码的。答案是.NET Framework SDK。一般安装了对应的框架的,可以在以下的两个路径下看到对应的文件路径。
%SystemRoot%\http://Microsoft.NET\Framework %SystemRoot%\http://Microsoft.NET\Framework64
.NET Framework SDK 包含一个命令行的工具叫做CLRVer.exe 可以查看当前的版本号。 ILAsm.exe可以将其中间语言汇编成对应的平台机器语言,当然也有对应的 IL Disassembler ILDasm.exe可以将对应的机器语言转换成对应的中间语言。
至于CLR的具体生效机制,这里通过一个打印"Hello"的控制台程序,来做下分析:
第一次执行Main中的代码的时候,需要先调用对应的MSCorEE.dll 通过JIT的方式生成Native CPU代码,这块本地代码有自己对应的内存空间,生成完成后返回这个内存空间进行代码的执行。

CLR 调用第一次执行的托管代码函数方法时通过MSCorEE.dll生成对应的Native CPU指令
第二次启动或者运行的时候,则是直接执行第一次启动通过JIT编译生成的本地代码(Native CPU instructions),不会再次使用MSCorEE.dll来编译生成新的代码,因此.NET的程序第一次启动运行会比较慢(相对而言,微软是做了大量优化)。

CLR第二次执行
另外在第一次启执行代码的过程中,可以指定执行IL的性能等级:

可以指定托管IL代码的执行效率
/debug:full 可以告诉JIT 编译器,接下来将要逐行调试对应的IL代码。
CLR 可以根据具体的运行环境来决定生成Native代码,而且可以调试或者重新编译IL代码到对应的Native代码。并且这个过程示可以指定对应的性能优化策略的。
这个时候,有同学可能会问了,如何手动生成对应的Native Code?
NGen.exe 是.NET Framework中可以将IL代码转换成native code 的工具。
已经解释了很多名词术语和对应的执行流程了,那么.NET Framework 到底是什么?
有人可能听说过.NET Framework,但是根本不知道其中到底包含什么,具体指的是什么,这里来具体说说,.NET Framework 中包含FCL( Framework Class Library ),总而言之,就是成千上万的dll和程序集。这些形形色色的程序集(Assemblies) 也是.NET Framework的实体对象。
其对外提供的能力或者说是开发人员可以使用的能力具体包含以下七个大的内容:
Web services
Web Forms/MVC HTML-based applications (websites)
Rich Windows GUI applications
Windows console applications
Windows services 通过.NET Framework 进行 Windows Service Control Manager (SCM)
Database stored procedures 各种数据库操作
Component library
说完可能没有直观感受,咱们再来看下以下熟悉的命名空间(是不是有熟悉的感觉了):

一些常用的FCL 命名空间
这些都是咱们编程的时候,经常遇到的命名空间,这样.NET Framework和 其对应的FCL应该就很具体了。
说到FCL就不得不提及CTS,那么什么是CTS呢?
CTS(Common Type System)跟CLR是同一个东西,只是描述的角度不同,这个是指具体的编码逻辑实现。
通俗的讲 就是微软所说的CLR 的基础实现单元,就是在那么多的动态库之下,到底是通过什么来封装和实现对应的功能的呢,答案很简单,是通过一系列Types来暴露对外开放能力和其接口API的。微软还为这些通用的,基础的Types,设立了标准和规范,只要按照这个标准接口去定义对应的变量,结构体,或者类型,就能与整个框架建立起通信,不同语言也能互通,即跨语言的对象创建,修改,保存,生命周期,内存管理等,另外也可以通过定义不同的类型来拓展其功能。
其中最核心的几个实体对象就是:
Field
Method
Property
Event
另外这个也是构成MetaData的基础数据类型。
除此之外,还有一个CLS需要重点介绍一下,那么什么是CLS?
我们已经知道CLR/CTS是.NET Framework的全集,那么CLS(Common Language Specification) 是COM (组件对象模型)在不同的语言之间进行彼此交互的最小子集。通俗的讲就是不同的编程语言都可以创建对应的COM,各种不同的对象之间可以彼此之间进行无差别的交互访问,不同语言之间最小最通用的“共识”集合。
这里不得不提及一些有趣的差异,比如有些语言不支持无符号整型,类型重载,有些语言大小写不敏感,像VB,或者函数不支持可变参数等,但是如果要设计能在CLR/CTS上运行的语言,就必须实现CLS。
这些概念之间的联系如下(举例说明):

概念之间的差异
当然这里还有一个unsafe 的概念没有做详细介绍,指的是为了追评本地代码的执行效率,托管语言中允许采用unsafe 标注的代码段来执行Native 代码,像指针等。
托管代码如何与非托管代码进行互操作?
由于.NET Framework 是个非常庞大的代码库,CLR中包含了众多开发者的成果。由于都有最基础的CLS,因此可以实现托管代码和非托管代码之间的交互。
具体体现在:
托管代码可以调用非托管代码在DLL中的函数
托管代码能够使用现存的COM或者组件(服务)
非托管代码可以使用托管代码的类型或者服务。
由于篇幅关系,后续再详细介绍具体的用法。 |
|