下来我要说什么是.NET的跨平台并解释为什么能够跨语言。不过要想知道什么是跨平台首先你得知道一个程序是如何在本机上运行的。什么是CPUCPU,全称Central Processing Unit,叫做中央处理器,它是一块超大规模的集成电路是计算机组成上必不可少的组成硬件没了它计算机就是个壳。无论你编程水平怎样你都应该先知道CPU是一台计算机的运算核心和控制核心CPU从存储器或高速缓冲存储器中取出指令放入指令寄存器并对指令译码执行指令。我们运行一个程序CPU就会不断的读取程序中的指令并执行直到关闭程序。事实上从电脑开机开始CPU就一直在不断的执行指令直到电脑关机。什么是高级编程语言在计算机角度每一种CPU类型都有自己可以识别的一套指令集计算机不管你这个程序是用什么语言来编写的其最终只认其CPU能够识别的二进制指令集。在早期计算机刚发展的时代人们都是直接输入01010101这样的没有语义的二进制指令来让计算机工作的可读性几乎没有没人愿意直接编写那些没有可读性、繁琐、费时易出差错的二进制01代码所以后来才出现了编程语言。编程语言的诞生使得人们编写的代码有了可读性有了语义与直接用01相比更有利于记忆。而前面说了计算机最终只识别二进制的指令那么我们用编程语言编写出来的代码就必须要转换成供机器识别的指令。就像这样code: 12 function 翻译方法(参数:code) { ... 1001; 2002; 000; return 能让机器识别的二进制代码; } call 翻译方法(12) 001 000 002所以从一门编程语言所编写的代码文件转换成能让本机识别的指令这中间是需要一个翻译的过程。而我们现在计算机上是运载着操作系统的光翻译成机器指令也不行还得让代码文件转化成可供操作系统执行的程序才行。那么这些步骤就是编程语言所对应的编译环节的工程了。这个翻译过程是需要工具来完成我们把它叫做 编译器。不同厂商的CPU有着不同的指令集为了克服面向CPU的指令集的难读、难编、难记和易出错的缺点后来就出现了面向特定CPU的特定汇编语言 比如我打上这样的x86汇编指令 mov ax,bx 然后用上用机器码做的汇编器它将会被翻译成 1000100111011000 这样的二进制01格式的机器指令.不同CPU架构上的汇编语言指令不同而为了统一一套写法同时又不失汇编的表达能力C语言就诞生了。用C语言写的代码文件会被C编译器先转换成对应平台的汇编指令再转成机器码最后将这些过程中产生的中间模块链接成一个可以被操作系统执行的程序。那么汇编语言和C语言比较我们就不需要去阅读特定CPU的汇编码我只需要写通用的C源码就可以实现程序的编写我们用将更偏机器实现的汇编语言称为低级语言与汇编相比C语言就称之为高级语言。在看看我们C#我们在编码的时候都不需要过于偏向特定平台的实现翻译过程也基本遵循这个过程。它的编译模型和C语言类似都是属于这种间接转换的中间步骤故而能够跨平台。所以就类似于C/C#等这样的高级语言来说是不区分平台的而在于其背后支持的这个 翻译原理 是否能支持其它平台。什么是托管代码托管语言托管模块作为一门年轻的语言C#借鉴了许多语言的长处与C比较C#则更为高级。往往一段简小的C#代码其功能却相当于C的一大段代码并且用C#语言你几乎不需要指针的使用这也就意味着你几乎不需要进行人为的内存管控与安全考虑因素也不需要多懂一些操作系统的知识这让编写程序变得更加轻松和快捷。如果说C#一段代码可以完成其它低级语言一大段任务那么我们可以说它特性丰富或者类库丰富。而用C#编程不需要人为内存管控是怎么做到的呢.NET提供了一个垃圾回收器(GC)来完成这部分工作当你创建类型的时候它会自动给你分配所需要的这部分内存空间。就相当于有一个专门的软件或进程它会读取你的代码然后当你执行这行代码的时候它帮你做了内存分配工作。 这部分本该你做的工作它帮你做了这就是“托管”的概念。比如现实中 托管店铺、托管教育等这样的别人替你完成的概念。因此C#被称之为托管语言。C#编写的代码也就称之为托管代码,C#生成的模块称之为托管模块等。(对于托管的资源是不需要也无法我们人工去干预的但我们可以了解它的一些机制原理在后文我会简单介绍。)只要有比较就会产生概念。那么在C#角度那些脱离了.NET提供的诸如垃圾回收器这样的环境管制就是对应的 非托管了。非托管的异常我们编写的程序有的模块是由托管代码编写有的模块则调用了非托管代码。在.NET Framework中也有一套基于此操作系统SEH的异常机制理想的机制设定下我们可以直接通过catch(e)或catch来捕获指定的异常和框架设计人员允许我们捕获的异常。而异常类型的级别也有大有小有小到可以直接框架本身或用代码处理的有大到需要操作系统的异常机制来处理。.NET会对那些能让程序崩溃的异常类型给进行标记对于这部分异常在.NET Framework 4.0之前允许开发人员在代码中自己去处理但4.0版本之后有所变更这些被标记的异常默认不会在托管环境中抛出(即无法catch到)而是由操作系统的SEH机制去处理。不过如果你仍然想在代码中捕获处理这样的异常也是可以的你可以对需要捕获的方法上标记[System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptionsAttribute]特性就可以在该方法内通过catch捕获到该类型的异常。你也可以通过在配置文件中添加运行时节点来对全局进行这样的一个配置runtime legacyCorruptedStateExceptionsPolicy enabledtrue / /runtimeHandleProcessCorruptedStateExceptions特性https://msdn.microsoft.com/zh-cn/library/azure/system.runtime.exceptionservices.handleprocesscorruptedstateexceptionsattribute.aspxSEHException类https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.sehexception(vvs.100).aspx处理损坏状态异常博客专栏 https://msdn.microsoft.com/zh-cn/magazine/dd419661.aspx什么是CLR.NET虚拟机实际上.NET不仅提供了自动内存管理的支持他还提供了一些列的如类型安全、应用程序域、异常机制等支持这些 都被统称为CLR公共语言运行库。CLR是.NET类型系统的基础所有的.NET技术都是建立在此之上熟悉它可以帮助我们更好的理解框架组件的核心、原理。在我们执行托管代码之前总会先运行这些运行库代码通过运行库的代码调用从而构成了一个用来支持托管程序的运行环境进而完成诸如不需要开发人员手动管理内存一套代码即可在各大平台跑的这样的操作。这套环境及体系之完善以至于就像一个小型的系统一样所以通常形象的称CLR为.NET虚拟机。那么如果以进程为最低端进程的上面就是.NET虚拟机(CLR)而虚拟机的上面才是我们的托管代码。换句话说托管程序实际上是寄宿于.NET虚拟机中。什么是CLR宿主进程运行时主机那么相对应的容纳.NET虚拟机的进程就是CLR宿主进程了该程序称之为运行时主机。这些运行库的代码全是由C/C编写具体表现为以mscoree.dll为代表的核心dll文件该dll提供了N多函数用来构建一个CLR环境 最后当运行时环境构建完毕(一些函数执行完毕)后调用_CorDllMain或_CorExeMain来查找并执行托管程序的入口方法(如控制台就是Main方法)。如果你足够熟悉CLR那么你完全可以在一个非托管程序中通过调用运行库函数来定制CLR并执行托管代码。像SqlServer就集成了CLR可以使用任何 .NET Framework 语言编写存储过程、触发器、用户定义类型、用户定义函数标量函数和表值函数以及用户定义的聚合函数。有关CLR大纲介绍 https://msdn.microsoft.com/zh-cn/library/9x0wh2z3(vvs.85).aspxCLR集成 https://docs.microsoft.com/zh-cn/previous-versions/sql/sql-server-2008/ms131052(v%3dsql.100)构造CLR的接口https://msdn.microsoft.com/zh-cn/library/ms231039(vvs.85).aspx适用于 .NET Framework 2.0 的宿主接口https://msdn.microsoft.com/zh-cn/library/ms164336(vvs.85).aspx选择CLR版本 https://docs.microsoft.com/en-us/dotnet/framework/configure-apps/file-schema/startup/supportedruntime-element所以C#编写的程序如果想运行就必须要依靠.NET提供的CLR环境来支持。 而CLR是.NET技术框架中的一部分故只要在Windows系统中安装.NET Framework即可。Windows系统自带.NET FrameworkWindows系统默认安装的有.NET Framework并且可以安装多个.NET Framework版本你也不需要因此卸载因为你使用的应用程序可能依赖于特定版本如果你移除该版本则应用程序可能会中断。Microsoft .NET Framework百度百科下有windows系统默认安装的.NET版本图出自 https://baike.baidu.com/item/Microsoft%20.NET%20Framework/9926417?fraladdin.NET Framework 4.0.30319在%SystemRoot%\Microsoft.NET下的Framework和Framework64文件夹中分别可以看到32位和64位的.NET Framework安装的版本。我们点进去可以看到以.NET版本号为命名的文件夹有2.0,3.0,3.5,4.0这几个文件夹。.NET Framework4.X覆盖更新要知道.NET Framework版本目前已经迭代到4.7系列电脑上明明安装了比4.0更高版本的.NET Framework然而从文件夹上来看最高不过4.0这是为何原来自.NET Framework 4以来的所有.NET Framework版本都是直接在v4.0.30319文件夹上覆盖更新并且无法安装以前的4.x系列的老版本所以v4.0.30319这个目录中其实放的是你最后一次更新的NET Framework版本。.NET Framework覆盖更新https://docs.microsoft.com/en-us/dotnet/framework/install/guide-for-developers如何确认本机安装了哪些.NET Framework和对应CLR的版本我们可以通过注册表等其它方式来查看安装的最新版本https://docs.microsoft.com/zh-cn/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed 。不过如果不想那么复杂的话还有种最直接简单的那就是进入该目录文件夹随便找到几个文件对其右键然后点击详细信息即可查看到对应的文件版本可以依据文件版本估摸出.NET Framework版本比如csc.exe文件。什么是程序集上文我介绍了编译器即将源代码文件给翻译成一个计算机可识别的二进制程序。而在.NET Framework目录文件夹中就附带的有 用于C#语言的命令行形式的编译器csc.exe 和 用于VB语言的命令行形式的编译器vbc.exe。我们通过编译器可以将后缀为.cs(C#)和.vb(VB)类型的文件编译成程序集。程序集是一个抽象的概念不同的编译选项会产生不同形式的程序集。以文件个数来区分的话那么就分 单文件程序集(即一个文件)和多文件程序集(多个文件)。而不论是单文件程序集还是多文件程序集其总有一个核心文件就是表现为后缀为.dll或.exe格式的文件。它们都是标准的PE格式的文件主要由4部分构成1.PE头即Windows系统上的可移植可执行文件的标准格式2.CLR头它是托管模块特有的它主要包括1)程序入口方法2)CLR版本号等一些标志3)一个可选的强名称数字签名4)元数据表主要用来记录了在源代码中定义和引用的所有的类型成员(如方法、字段、属性、参数、事件...)的位置和其标志Flag(各种修饰符)正是因为元数据表的存在VS才能智能提示反射才能获取MemberInfoCLR扫描元数据表即可获得该程序集的相关重要信息所以元数据表使得程序集拥有了自我描述的这一特性。clr2中元数据表大概40多个其核心按照用途分为3类1.即用于记录在源代码中所定义的类型的定义表ModuleDef、TypeDef、MethodDef、ParamDef、FieldDef、PropertyDef、EventDef2.引用了其它程序集中的类型成员的引用表MemberRef、AssemblyRef、ModuleRef、TypeRef3. 用于描述一些杂项(如版本、发布者、语言文化、多文件程序集中的一些资源文件等)的清单表AssemblyDef、FileDef、ManifestResourceDef、ExportedTypeDef3.IL代码(也称MSIL后来被改名为CILCommon Intermediate Language通用中间语言)是介于源代码和本机机器指令中间的代码将通过CLR在不同的平台产生不同的二进制机器码。4.一些资源文件多文件程序集的诞生场景有比如我想为.exe绑定资源文件(如Icon图标)或者我想按照功能以增量的方式来按需编译成.dll文件。 通常很少情况下才会将源代码编译成多文件程序集并且在VS IDE中总是将源代码给编译成单文件的程序集(要么是.dll或.exe)所以接下来我就以单文件程序集为例来讲解。用csc.exe进行编译现在我将演示一段文本是如何被csc.exe编译成一个可执行的控制台程序的。我们新建个记事本然后将下面代码复制上去。View Code然后关闭记事本将之.txt的后缀改为.cs的后缀(后缀是用来标示这个文件是什么类型的文件并不影响文件的内容)。上述代码相当于Web中的http.sys伪实现是建立了通信的socket服务端并通过while循环来不断的监视获取包的数据实现最基本的监听功能最终我们将通过csc.exe将该文本文件编译成一个控制台程序。我已经在前面讲过BCL基础类库。在这部分代码中为了完成我想要的功能我用到了微软已经帮我们实现好了的String数据类型系列类(.NET下的一些数据类型)、Environment类(提供有关当前环境和平台的信息以及操作它们的方法)、Console类(用于控制台输入输出等)、Socket系列类(对tcp协议抽象的接口)、File文件系列类(对文件目录等操作系统资源的一些操作)、Encoding类(字符流的编码)等这些类都属于BCL中的一部分它们存在但不限于mscorlib.dll、System.dll、System.core.dll、System.Data.dll等这些程序集中。附不要纠结BCL到底存在于哪些dll中总之它是个物理分散逻辑上的类库总称。mscorlib.dll和System.dll的区别https://stackoverflow.com/questions/402582/mscorlib-dll-system-dll因为我用了这些类那么按照编程规则我必须在代码中using这些类的命名空间并通过csc.exe中的 /r:dll路径 命令来为生成的程序集注册元数据表(即以AssemblyRef为代表的程序集引用表)。而这些代码引用了4个命名空间但实际上它们只被包含在mscorlib.dll和System.dll中那么我只需要在编译的时候注册这两个dll的信息就行了。好接下来我将通过cmd运行csc.exe编译器再输入编译命令 csc /out:D:\demo.exe D:\dic\demo.cs /r:D:\dic\System.dll/r是将引用dll中的类型数据注册到程序集中的元数据表中 。/out:是输出文件的意思如果没有该命令则默认输出{name}.exe。使用csc.exe编译生成 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/compiler-options/command-line-building-with-csc-execsc编译命令行介绍.CSC.exe编译器使用 - 双魂人生 - 博客园总之你除了要掌握基本的编译指令外当你打上这行命令并按回车后必须满足几个条件1.是.cs后缀的c#格式文件2.是 代码语法等检测分析必须正确3.是 使用的类库必须有出处(引用的dll)当然 因为我是编译为控制台程序所以还必须得有个静态Main方法入口以上缺一不可。可以看出这段命令我是将 位于D:\dic\的demo.cs文件给编译成 位于D:\名为demo.exe的控制台文件并且因为在代码中使用到了System.dll所以还需要通过/r注册该元数据表。这里得注意为什么没有/r:mscorlib.dll,因为mscorlib.dll地位的特殊所以csc总是对每个程序集进行mscorlib.dll的注册(自包含引用该dll),因此我们可以不用/r:mscorlib.dll这个引用命令但为了演示效果我还是决定通过/nostdlib命令来禁止csc默认导入mscorlib.dll文件。所以最终命令是这样的 csc D:\dic\demo.cs /r:D:\dic\mscorlib.dll /r:D:\dic\System.dll /nostdlib因为没有指定输出文件/out选项 所以会默认输出在与csc同一目录下名为demo.exe的文件。事实上在csc的命令中如果你没有指定路径那么就默认采用在csc.exe的所在目录的相对路径。而我们可以看到在该目录下有许多程序集其中就包含我们需要的System.dll和mscorlib.dll所以我们完全可以直接/r:mscorlib.dll /r:System.dll而类似于System.dll、System.Data.dll这样使用非常频繁的程序集我们其实不用每次编译的时候都去手动/r一下对于需要重复劳动的编译指令我们可以将其放在后缀为.rsp的指令文件中然后在编译时直接调用文件即可执行里面的命令 {name}.rsp。csc.exe默认包含csc.rsp文件,我们可以用/noconfig来禁止默认包含而csc.rsp里面已经写好了我们会经常用到的指令。所以最终我可以这样写 csc D:\dic\demo.cs 直接生成控制台应用程序。.NET程序执行原理好的现在我们已经有了一个demo.exe的可执行程序它是如何被我们运行的。C#源码被编译成程序集程序集内主要是由一些元数据表和IL代码构成我们双击执行该exeWindows加载器将该exe(PE格式文件)给映射到虚拟内存中程序集的相关信息都会被加载至内存中并查看PE文件的入口点(EntryPoint)并跳转至指定的mscoree.dll中的_CorExeMain函数该函数会执行一系列相关dll来构造CLR环境当CLR预热后调用该程序集的入口方法Main()接下来由CLR来执行托管代码(IL代码)。JIT编译前面说了计算机最终只识别二进制的机器码在CLR下有一个用来将IL代码转换成机器码的引擎称为Just In Time Compiler简称JITCLR总是先将IL代码按需通过该引擎编译成机器指令再让CPU执行在这期间CLR会验证代码和元数据是否类型安全(在对象上只调用正确定义的操作、标识与声称的要求一致、对类型的引用严格符合所引用的类型)被编译过的代码无需JIT再次编译而被编译好的机器指令是被存在内存当中当程序关闭后再打开仍要重新JIT编译。AOT编译CLR的内嵌编译器是即时性的这样的一个很明显的好处就是可以根据当时本机情况生成更有利于本机的优化代码但同样的每次在对代码编译时都需要一个预热的操作它需要一个运行时环境来支持这之间还是有消耗的。而与即时编译所对应的就是提前编译了英文为Ahead of Time Compilation简称AOT也称之为静态编译。在.NET中使用Ngen.exe或者开源的.NET Native可以提前将代码编译成本机指令。Ngen是将IL代码提前给全部编译成本机代码并安装在本机的本机映像缓存中故而可以减少程序因JIT预热的时间但同样的也会有很多注意事项比如因JIT的丧失而带来的一些特性就没有了如类型验证。Ngen仅是尽可能代码提前编译程序的运行仍需要完整的CLR来支持。.NET Native在将IL转换为本机代码的时候会尝试消除所有元数据将依靠反射和元数据的代码替换为静态本机代码并且将完整的CLR替换为主要包含垃圾回收器的重构运行时mrt100_app.dll。