运行机制---进阶编程篇(一)

我已经假定你安装了宇宙第一IDE:visual studio 2017版本了,一个好的IDE会让你在敲代码的时候获得一个好心情,想想多年以前,很多人还是用VC++6.0,估计在现在,还是有一部分的人在使用,那个IDE用起来非常的不顺手,连提示错误都没有,很多时候我们只是手贱打错了个字,为了查错那个辛苦啊。扯远了,反正我没用过VC++6.0,如果你开始使用,推荐也是从最新版开始吧。

我们开始新建第一个winform项目,点击新建项目后就弹出来下面的对话框:

新建完成后,就是如下的界面了:

这时候点击F5运行,然后就是这样:

此处就出现了一个窗体,操作熟练的话,整个过程不到5秒就创建成功一个窗体了,一切都是那么的自然,自然的让我们以为创建窗口就应该是这个样子的,简单,高效,不得不说,这也是微软这么多年来努力的目标之一,就是让大家在开发程序的时候,可以尽可能的快速,便捷式操作,傻瓜式操作,使得编程的门槛大幅度的降低,上述的操作交给一个完全的新手时,几分钟就教会了。然后教教他怎么显示文本,怎么改变文本,估计也是几分钟的事情,就可以写一个简单的程序了。微软这么做的结果就是降低编程门槛,越来越多的人来学习C#,但也导致了水平普遍偏低的结果,就比如创建这个窗口,你在创建一百个这样的窗口,水平也没有提升再多了,因为这简单的几部它就是没什么技术含量的。

终于进入到了本文的重点了,讲解我们用C#编写的程序的运行机制,这部分的内容对于新人来讲确实挺难理解的,我一开始学习的时候简直就是看天书一样的,所以我会尽量的将我所知道的用平实的语言讲出来,这部分内容对于理解C#来说至关重要,也是进阶的必经之路。所以我先大概说明一下运行机制,再分部完成。

  就比如上面生成的exe程序,它到底是什么玩意,我想很多人都会疑问,刚学习C#的我也是这样。

  1. 我们首先在VS中写了很多的代码,点击生成或是调试的时候,IDE使用了C#编译器将我们写的所有的代码编译成了一种中间语言IL语言,写入到了生成的exe中。
  2. 可以想想看exe包含了什么东西,我们在上述的项目中定义了新的类Form1,那么这些类系统又不提供,所以肯定会把类写入到exe中,只要是自定义的类肯定会写入进去。
  3. 我们可以猜测exe文件应该还有个文件头,来标识这是一个可执行的Win32程序,我们在创建项目时可以选择.NET版本,应该还包含了环境版本

  到这里我们的猜测已经非常的接近事实的情况了, 所以当我们点击exe程序的时候,windows到底干了什么东西。

  

  首先windows会检查exe文件的文件头,检查程序类型是不是PE32文件头还是PE32+文件头的,这个文件头要求程序的运行环境,是不是32位,还是64位的,如果和操作系统不匹配,则不会运行。

  windows检查完exe后,检查合格后,接下来就要创建程序需要运行的进程空间了,在进程的地址空间加载MSCorEE.dll(一个.NET framework自带的链接库,可以在安装目录找到)。

  MSCorEE.dll的用处非常大,进程的主线程会调用组件的方法来初始化运行的CLR,然后加载exe的数据(就是中间语言IL代码,包含了所有的类型说明和数据,即使加载了,还是IL代码,还不不能直接运行的,如果你的exe还引用了其他的dll,那么所有的关联的dll都会加载进来)。

  然后MSCorEE.dll组件调用Main方法,这个和我们大学学的C语言是一致的,但是问题出现了,我们上面说过这时候CLR加载的还是IL代码,IL代码又不能直接运行,所以CLR内部的JITCompiler方法就出来干活了,工作是将IL代码编译成本机的CPU指令,这样操作后Main方法才可以真正的运行,如果Main方法调用了其他方法(这不是废话么),那么JITCompiler又出来工作了,如果每次调用方法,都要重新编译一次,那么应用程序的性能就非常差了,所以CLR使用了缓存的机制,所有的方法只有在第一次调用的时候存在一点性能损耗,以后调用就直接使用了本地指令。

  绕着这么多的弯路,终于窗体运行了,展示给你看了,汗颜-------

  这里肯定会有小伙伴跳出来说,这么搞累不累啊,我也想说,确实挺累的,为了运行一个程序,绕了一个大弯,我还记得我在大学的时候学的单片机,虽然我使用C语言来写程序的,但是编译器直接编译成了汇编写入ROM中,然后指针就可以直接调用程序了,你看,多么的简单明了。相比较而言,我们就不能用C#写的程序生成windows直接可以运行的程序吗?或者说直接生成IL的exe,再编译一次直接生成机器语言多好,运行效率会高很多。

  要想回答这个问题,我们需要探究一下IL语言为什么会产生,大致就能回答上述的问题了,说到IL的产生,又不得不说.NET的生成历史,当你微软看到JAVA发展如你中天时,心里也痒痒了,也想做一个新的语言,运行在虚拟机上,这样的程序和平台无关,可以实现程序的方便移植,虽然后来做着做着还是绑定了windows系统,但是至少和windows版本没有了太大的关系,比如上述的exe程序,是基于.NET 4.5的,只要安装了.NET 4.5的话就可以很好的支持,不必为了针对哪个系统,哪个CPU架构来区别对待。事实上基于CLR的IL可以实现的功能非常的强大,拥有完整的面向对象的机制,提供了抛出异常和处理模型,只是在C#层面就只能实现部分的CLR功能,也就是说,如果CLR不支持的功能,在C#层面就肯定不支持了。

  如果直接生成了基于机器指令的exe程序,事实上微软完全可以做到,但是这也丧失了动态性能,JITCompiler在即时编译指令时,会根据当前的cpu来优化本地指令,为特定cpu生成的程序有可能会在其他电脑无法运行,这就失去了程序的健壮性,谁都不会希望开发出这种程序吧。

  关于性能损耗,微软早就意识到这个问题了,花了非常大的人力物力来优化JIT的性能损耗,在我的经验中,性能非常的高效,已经满足绝大多数的场景。

  到此处为此,其实已经说的非常完整了,需要多读几遍,才能比较好的理解,也可以自己去查查看IL代码是什么样子的,相信还有很大的收获的。

时间: 2024-12-17 08:29:53

运行机制---进阶编程篇(一)的相关文章

类型本质---进阶编程篇(二)

我们在学习一门新的编程语言时,永远都绕不开变量类型和控制语句,这两大块是一个程序的基本构成方式,而且我们也知道构成计算机数据的一切本质其实都是0和1,比如你运行的程序是0和1组成的,你播放的一首歌也是0和1组成的,你看的电影也是0和1组成的,所以一个数据对象肯定也是0和1组成的,一个数据对象没有类型是没有办法想象的,同样是4个字节的数据,以int,uint,float来看待都是不一样的结果,也就是如果我们设置的数据类型和读取的数据类型不一致时,绝大多数情况都会导致意外发生. 本篇文章将会从两个不

操作系统运行环境与运行机制(系统调用篇)

系统调用: 用户在编程是可以调用的操作系统功能(使CPU可以从用户态陷入内核态)  应用程序,C函数,API,和内核函数关系 系统调用机制的设计 ①中断/异常机制 支持系统调用服务的实现:选择一条陷入指令(访管指令)即可 ②选择一条特殊指令:陷入指令(亦称访管指令) 引发异常完成用户态到内核态的切换 ③系统调用号和参数: 每个系统调用都实现给定一个编号(功能号) ④系统调用表: 存放系统调用服务例程入口地址 参数传递问题(怎样实现用户程序的参数(存在于用户栈)传递给内核(存在于内核栈)?): ①

数组使用---进阶编程篇(五)

本篇文章讲解数组的使用,先是介绍下几种不同的数组,在说明下各自的区别和使用场景,然后注意细节,废话不多说,赶紧上代码. 在.Net 3.5之中,我们常用的数组基本就是如下的几种方式(词典Dictionary<TKey,TValue>比较特殊,下面单独解释): ArrayList 方式的数组 T[] 方式的数组 List<T> 方式数组 Queue<T> 先进先出的数组 Stack<T> 后进先出的数组 ArrayList: 简单的说,ArrayList是一个

类的使用---进阶编程篇(四)

很多讲解编程的书籍在介绍类的时候就直接介绍属性,字段,方法,事件等等,然后说下多态性,继承性,等等,所有的这些东西对于初学者来说实在是虚无缥缈,对于什么地方该用类,该怎么设计类仍然是很模糊的,需要经验大量的编程实践才能摸索到里面的经验,所以本节在讲解的时候,会侧重于举例子来说明,为什么需要使用类,怎么使用类,至于类的继承也会针对特定的情况来说明,让大家对于类有个清晰的概念. 为什么需要使用类 理解这个问题对于学习编程的人非常的关键,对于有经验的人来说,可能会回答,类可以分离功能,形成功能模块,进

谈谈Java运行机制

转载地址:http://www.cnblogs.com/iwinson/p/6074885.html 1.高级语言的运行机制 我们编程都是用的高级语言(写汇编和机器语言的大牛们除外),计算机不能直接理解高级语言,只能理解和运行机器语言,所以必须要把高级语言翻译成机器语言,计算机才能运行高级语言所编写的程序. 翻译,其实翻译的方式有两种,一个是编译,一个是解释.两种方式只是翻译的时间不同. 1.1编译型语言    (如C.C++.Objective-C) 使用专门的编译器,针对特定平台(操作系统)

解密SparkStreaming运行机制和架构进阶之Job和容错(第三篇)

本期要点: 1.探讨Spark Streaming Job架构和运行机制 2.探讨Spark Streaming 容错机制 关于SparkStreaming我们在前面的博客中其实有所探讨,SparkStreaming是运行在SparkCode之前的一个子框架,下面我们通过一个简单例子来逐一探讨SparkStreaming运行机制和架构 SparkStreaming运行机制和架构 //新浪微博:http://weibo.com/ilovepains/ SparkConf conf = new Sp

第3课:通过案例对SparkStreaming 透彻理解三板斧之三:解密SparkStreaming运行机制和架构进阶之Job和容错

理解Spark Streaming的Job的整个架构和运行机制对于精通Spark Streaming是至关重要的. 一 首先我们运行以下的程序,然后通过这个程序的运行过程进一步加深理解Spark Streaming流处理的Job的执行的过程,代码如下: object OnlineForeachRDD2DB { def main(args: Array[String]){ /* * 第1步:创建Spark的配置对象SparkConf,设置Spark程序的运行时的配置信息, * 例如说通过setMa

第3课:SparkStreaming 透彻理解三板斧之三:解密SparkStreaming运行机制和架构进阶之Job和容错

本期内容: 解密Spark Streaming Job架构和运行机制 解密Spark Streaming容错架构和运行机制 理解SparkStreaming的Job的整个架构和运行机制对于精通SparkStreaming是至关重要的.我们知道对于一般的Spark应用程序来说,是RDD的action操作触发了Job的运行.那对于SparkStreaming来说,Job是怎么样运行的呢?我们在编写SparkStreaming程序的时候,设置了BatchDuration,Job每隔BatchDurat

spark版本定制:SparkStreaming 透彻理解三板斧之三:解密SparkStreaming运行机制和架构进阶之Job和容错

本期内容: 1.解密Spark Streaming Job架构和运行机制 2.解密Spark Streaming 容错架构和运行机制 一.解密Spark Streaming Job架构和运行机制 通过代码洞察Job的执行过程: object OnlineForeachRDD2DB { def main(args: Array[String]){ /* * 第1步:创建Spark的配置对象SparkConf,设置Spark程序的运行时的配置信息 */ val conf = new SparkCon