Windows零基础入门:1.7 程序如何执行和程序入口知识

4782 人浏览 | 时间: 2015-07-29 14:20:17 | 作者: codexia 会员文章,禁止转载
    放眼望去,各种教程,各种培训,各种学校上课,各种书籍,大凡开启编程学习大门的,都是那么一个经典的Hello World。就这么一个开始,也就铺开了我们编程之路,成为我们人生的里程碑。踏上编程之路,注定我们对于世间万物能够更加细致的洞察和分析,这是我们对于人生的历练,而不只是写代码而已。
    Hello World承载了我们程序员太多的希望和憧憬,每一位怀揣梦想的人,走进了编程的世界,可是很多都走不到最后而放弃,甚至走到不远就迷茫了。我们缺乏智者的引导,没有很好的环境,太多的现实环境影响,以至于很多时候,我们身不由己。
    而我试图带领大家前入编程世界,却并没有开门见山,横刀植入。进入编程世界,不难,难就难在,你能不能在这个世界灵活自由的展现自己的魅力,而不是进入这个世界成为一个机器。
    代码的世界不需要机器,你的一个月的时间的机械劳作,换做一组代码,可能就完成了。而你可能一直因为某个原因,而不敢尝试,所以一直机械式的劳作,愿意成为机器,只为完成那么一些任务。我不会嘲笑你,也不会同情你,我们都不能幸免于此。而我们需要做的,就是不断地历练自己,提高自己的境界,提高自己的水平,以求不断的突破自己。如此,你将可以挣脱机器的工作方式。每一遍的工作都可以生华,不断改进。
    我之前要实现一个窗口透明的效果,结果得到了一串过时的代码,不过现在确实可用。当时我Windows方面的知识不多,所以,我就深入MSDN学习,全部是英文资料,不过终于得到精髓。后面,提炼后,一句代码搞定。而这一段代码,却被网络流传。这样的情景,看着就心痛,这么简单一个东西,竟没有人去优化,明明是过时的,却还没广泛使用。这个技术分析在《实现窗口透明(透明窗口和不规则窗口)的原理分析》可以看。
    而我对此课程的要求,就是真正受益于程序员本身,挖掘精髓。或许,我自己就是这么一个喜欢挖掘研究,喜欢追根溯源的人,所以,自学的时候,也就会思考各种问题,试图求解,然而,目前的教程,几乎没有。当然,在相应的更高深的书籍里都会讲到,而都是孤立的,你无从得知,除非你博览群书。这也就是博览群书的好处,各种信息最终汇聚到你的脑子,你才会恍然大悟。而此课程,我力求将我所知所不知,尽可能分享给大家。
    这样会保证大家真正学的很好,不过,这个过程会稍微与常规课程不一样,进度可能也不像常规那样。这也是本课程为什么现在还没有到达hello world的原因。请相信我,此课程跟着学完,你对Windows的认知,比任何现在的其他Windows培训课程掌握的更加透彻,而且全面深入,时间也是最少的,成本也是几乎为零的。要说成本,就是要耐心看学习。以文章形式,才能做到这样的效果,所以大家不要太在意课程的形式。如果后面你觉得课程很好,很有价值,请宣传一下,让更多初学者自学者受益。本课程作为公益课程,旨在普及编程,后面还会推出更多的课程,尽请关注。
    好了,这节课我们来好好了解一下程序入口这些东西。先来说说背景知识,然后结合程序了进一步说明。
    我们所运行的程序,都是要运行在一个平台上的。而所谓的平台,通常来理解就是操作系统。Windows操作系统就是其中一个。其他系统,比如Linux、iOS、Android等等,都是的。而操作系统在程序级别来看,就是一堆程序的有机组合。那么也就是说,操作系统也是程序,那么这个程序如何运行呢?
    先不说我们写的程序,要了解这个,我先看看操作系统大概的运作过程。这里并不讲操作系统如何一步步运行起来的。我们介绍的是操作系统的其他一些东西。类比来看,我们的程序就好比是战斗机,而操作系统则是航空母舰。这个比喻很恰当。实际上,航空母舰就可以看做是战斗机的运行平台。当然,差别还是有的,这里就是大概类比一下,好有个形象的对比。
    操作系统作为一组程序,所以它要先运行起来。那么操作系统的程序运行,要运行在什么平台呢?操作系统是介于硬件和软件之间的。硬件就是电脑手机等硬件设备,而软件就是我们写的软件咯。操作系统的程序要运行的平台就是我们常说的硬件环境,可以理解为硬件平台。比如操作系统要运行在X86硬件上,有的系统不支持IA64硬件架构等等。这些名词或许你听过了。实际上,这里指的就是操作系统要运行的硬件环境,而只要系统运行起来了,我们写的程序都可以运行在系统之上,而不用管硬件这些东西。我们这些程序一般都叫做应用程序。我们程序要完成的功能,都是通过操作系统来完成的,这就是课程前面讲到的API调用的原理。
    那么我们程序就生死都在操作系统上,能不能实现什么功能,就要看操作系统支不支持,只要操作系统支持,我们程序就能够实现,因为我们程序要实现的功能都是委托操作系统完成的。操作系统能力有多大,我们程序就能够有多强大!
    硬件厂商给操作系统提供了硬件的环境,让操作系统可以运行起来,而操作系统则给我们程序提供了软件环境(操作系统就是一组程序软件),使我们程序可以运行在操作系统中。那么,在电脑运行过程中,我们看到的是操作系统在运作。你对电脑的所有操作(除了你直接操作硬件等),都要经过操作系统来处理。你的程序运行,自然也在操作系统的管理之下。也就是说,操作系统负责了电脑的各种程序硬件的管理使用。所以,你的程序要启动,其实是一个很复杂的过程,并不是你知道的只要写个main之类的函数那么简单。
    我们需要深入了解这些背景,在编程的时候,我们就知道为什么这个mian就可以开始执行我们程序了。为什么很多Winddows游戏只要一个简单的WinMain进入就可以了,其他的都不要微软的东西。因为只要进入了WinMain函数,就等于系统把电脑的使用权交给了你的程序,你的程序具体如何使用,不必都要用微软的东西。
    在系统把使用权交给我们的这个过程,就是系统安排我们程序运行的过程,也就是准备进入我们程序的入口函数main或者WinMain的过程。操作系统时刻都在运行中,除非你关机断电了。而负责管理各个程序运行的部分就是系统的调度程序。它一直和交通警察一样的,管理进程的运作。当你双击的exe程序时,系统会检测到你的鼠标的动作,从而进行处理。如果发现你双击的是某个exe,系统发现你想要执行一个程序,便会安排让你的程序执行。而这个安排的人就是系统的调度程序。调度程序分析我们的exe,获取程序的类型,然后才能知道我们程序需要什么基础环境。这里说的基础环境,指的是,程序要运行需要的基础运行库。我们用C语言写的程序需要C运行时库,C++的则需要C++运行时库等等,其他的程序自然也需要这些基本库。这些库与系统无关。你在开发时,选用的开发环境和工具,都会决定程序是什么类型,这个与前面说的程序的运行平台不一样。Windows程序运行的平台环境是Windows操作系统,而这个系统中还有各种基础环境,保证这个程序能够正常运行的。一般这些都叫做运行时库。我们用C/C++开发的,如果没有C/C++运行时库的支持,系统就无法启动你的程序了。
    下面来看一个图示。
程序如何启动如何进入入口函数示意图 
    图中展示的是一个操作系统的调度程序的示意图。我们双击了exe,系统先捕获的这个动作,将这个请求放入调度队列,然后调度程序再调度运行。调度程序要先要根据程序的类型,来启动对应需要的运行时库,然后才进入到我们程序执行。而这运行时库,是我们程序运行起来的基础支持,就像需要先打开嘴巴,才能吃饭一样。运行时库简单来说,就好像是你这个程序需要的管家。它时刻在关注程序的运行,如果程序崩溃异常,这个运行时库会知道的,从而做出处理。当然,运行时库运行在系统的监控之内。运行时库有点像你的程序的保姆,同时与操作系统保持联系,算是操作系统和你程序的中间联系人。如此来理解一下运行时库,也就不难懂了吧。为什么要做运行时库,因为你程序运行时需要用到这个基本库咯。而这个运行时库,需要由系统来启动运行。
    总结来看,我们的程序进入到入口函数之前,是发生了很多事情的。操作系统的安排,启动运行时库,运行时库再初始化好环境,然后启动你的入口函数,你的程序才正常的运行起来。等你的程序运行结束后,就退回到运行时库,然后再退回到操作系统,然后系统再调度其他程序执行。
    下面一个简单的程序,从代码上看看这个效果。我们写这个代码如下:
void main()
{
    int i = 0;
}

    然后再这个唯一几句代码里打个断点。光标放在这句代码上,按F9即可。打了断点后,按F5进入调试,调试的界面如下:
    调试程序,设置断点
    这个箭头表示,程序已经进入了我们的程序,那么我们来看看进入的过程的代码执行过程。在VS界面上找到调用哦堆栈小窗口,然后你会找到以下调用堆栈窗口:
    main启动过程调用堆栈
    如果你看到的不是这样的,有很多问号的,或者显示什么不可用符号等等,在对应的那条上面,右击点击显示或导入“符号”的菜单,然后VS自动更新符号,这样就可以显示出这些函数分符号名了。
    堆栈的特点就是先进后出,先进的在底部,这里就是这样的。
    执行的顺序从底部到顶部,从顶部可以看出,后面的main()表示正在执行到main函数中了。我们从最底部开始往上看。底部的两条,ntdll.dll是Windows系统的一个核心库,也是系统的核心功能库之一,后面的RtlUserThreadStart表示的就是系统在启动我们的exe,并创建了一个进程主线程。然后,第三句kernel.dll这个库里执行了BaseThreadInitThunk执行了我们的进程的主线程的初始化工作,包括分配线程内存等。
    然后基本的系统初始化工作都执行完毕,然后就要开始启动我们的主线程执行了。这个过程就是图中说的启动程序到调度程序做一些初始化工作。接下来就会去启动运行时库。在接下来的五个函数执行中,都可以看到前面ConsoleApplication3开头,这个是我们的程序文件名,这表示这几个函数都是为我们程序服务的,这些都是运行在我们程序的进程空间的,其实就是我们程序所占的内存块中。mainCRTStartup()函数的CRT就是C RunTime(C运行时库)的意思,这里就是C运行时库的函数了,它在准备启动main函数的执行了。不过这里才刚刚启动,是在做初始化运行时环境,就是调用后面的函数__scrt_common_main()。这个函数中做了基本的运行时环境初始化后,又调用__scrt_common_main_seh()。这个函数也做了一系列的初始化工作,然后调用invoke_main()函数,去调用main函数运行。
    invoke_main()函数代码如下:
static int __cdecl invoke_main() throw()
{
    return main(__argc, __argv, _get_initial_narrow_environment());
}

    你可以看到,这个就是一个简单的调用而已,就这样就进入了我们的main函数的执行。而对于这个几个函数的代码,你可以直接在调用堆栈中双击就可以看到了。
    调用堆栈中,上一个函数是被底下那个函数所调用的,所以这个叫做调用堆栈。
    综上所述,你可以从上部分描述中感受到这个过程,在下面的代码级别中,又再一次验证了这个过程,想必对此过程一定更加影响深刻了。而我们的程序代码就是在这个过程完成后,进入到我们的入口函数开始执行的。
    然后程序执行完毕后,调用堆栈的函数依次执行完退出,最终又回到了系统的调度函数中执行其他程序。
    本节内容可能比较多,需要仔细理解。不过,我们的课程,这个只是需要让大家对此有个深刻的认识罢了,并不需要现在深入研究。当然,这个过程对应于其他的程序类型,也差不多,你可以自己动手跟踪一下,可以举一反三。一下子不明白,可以多看几遍。还有问题,请在后面提问。
当前文章为会员文章,请前往[用户中心]开通会员后继续阅读。

Win32课程菜单