我是荔园微风,作为一名在IT界整整25年的老兵,今天说说Windows程序的运行机制。经常被问到MFC到底是一个什么技术,为了解释这个我之前还写过帖子,但是很多人还是不理解。其实这没什么,我在学生时代也被这个问题困绕过。而且那个时间学习资料没有那么丰富,网上也没有什么资料,周围也没有懂的人,那个时候理解MFC更困难。甚至在我看来,理解这个比理解人工神经网络更难。
我认为造成这种现象的根本原因就是没有搞清楚Windows程序的运行机制,因为不理解Windows程序的运行机制,所以给理解MFC带来了很大的困难。我决定带所有微软开发技术的初学者一起攻破这个问题,但是一篇文章肯定是讲不清楚的,我们要分好几章来说。需要你有足够的耐心,一起来吧。我们这次先来搞清楚什么是Windows程序的句柄。
句柄?难道是一个长长勺子?这个名字让很多人产生了很大的误解。
很多初学者或编程新手在编写C程序的时候,经常会调用各种库函数来辅助完成某些功能,比如大家使用最多的C库函数就是printf()和scanf(),这些库函数是由你所使用的编译器开发者提供的。而在Windows平台下,也有类似的函数可供调用,不同的是,这些函数是由Windows操作系统本身提供的。Windows操作系统提供了各种各样的函数,数量巨大,以方便我们开发 Windows应用程序。这些函数是Windows操作系统提供给应用程序编程的接口,简称API函数。我们在编写Windows程序时所说的API函数,就是指系统提供的函数。
前面说,这些函数数量巨大,有多少呢?Windows操作系统整整提供了1000多种API函数作为开发人员日常开发用!这......考验我们的记忆力?要全部记住这些函数调用的语法?我们做不到啊。那么我们该怎么办呢?如何才能更好地去使用这些函数呢?微软提供的API函数大多是有意义的单词的组合,每个单词的首字母大写,比如CreateWindow用来为程序创建一个窗口,ShowWindow用于显示窗口,LoadIcon用于加载图标,SendMessage用于发送消息。我们就按这种规律去识别函数的用途。
微软技术中总是提到一个概念: Win32 SDK开发,那么什么是 SDK呢。SDK的全称是Software Development Kit,中文译为软件开发包。其实就是Windows 32位平台下的软件开发包,包括API函数、帮助文档以及微软提供的一些辅助开发工具。
举个例子,如果我们要和一家摄像头厂商合作开发一个街区的视频监控项目,摄像头厂商会提供他们的摄像头产品,同时还会提供摄像头的SDK开发包,以让我们对摄像头进行编程操作,比如移动摄像头的位置、调取拍摄到的图像、图像生成成结构化数据再传送等功能。这个开发包通常会包含摄像头的API函数库、帮助文档、使用手册、辅助工具等资源。
本文作为我们攻克MFC概念的第一篇重要文章,我们必须先攻克句柄这个小高地,不然后面的理解会很困难。下面我们就对这个高地发起进攻!
窗口是Windows应用程序中最重要的元素,一个Windows应用程序至少要有一个窗口,称为主窗口。窗口是屏幕上的一块矩形区域,是Windows应用程序与用户进行交互的接口。利用窗口,可以接收用户的输入和显示输出。一个应用程序窗口通常包含标题栏、菜单栏、左上角菜单、最小化、最大化、边框、滚动条。窗口可以分为客户区和非客户区,客户区是窗口的一部分,应用程序通常在客户区中显示文字或者绘制图形。标题栏、菜单栏、左上角菜单、最小化和最大化、边框统称为窗口的非客户区,由Windows系统来管理,而应用程序则主要管理客户区的外观及操作,换句话说,我们编程只能管到这个客户区,其他我们管不到,因为这是操作系统来干这个事的。
窗口可以有一个父窗口,有父窗口的窗口称为子窗口。另外,对话框和消息框也是一种窗口。在对话框上通常还包含许多子窗口,这些子窗口的形式有按钮、单选按钮、复选框、组框、文本编辑框等。我们再深入认识一下窗口,其实在启动Windows系统后,看到的桌面也是一个窗口,称为桌面窗口,它由Windows系统创建和管理。
为什么上面我要弄一个WIN95的图,因为越是这种旧的操作系统越能看到微软窗口最初设计的本来样子。现在的系统的窗口做的都太花哨了,反而容易让人误解,看不出窗口的感觉。
好了,重点来了。什么是句柄?
在Windows应用程序中,窗口是通过窗口句柄(HWND)来标识的。我们要对某个窗口进行操作,首先就要得到这个窗口的句柄。句柄(HANDLE)是Windows程序中一个重要的概念。在Windows程序中,有各种各样的资源(窗口、图标、光标、画刷等),系统在创建这些资源时会为它们分配内存,并返回标识这些资源的标识号,即句柄。对,你没有看错,句柄句柄不是一个有着长长柄的勺子,而是一个标识号!
下面我们把这几个概念强化一下:
句柄Handle
Handle是一个32位的无符号整数,它是一个内核对象的有效抓手。它并不指向实际的内核对象,用户模式下的程序一般情况下不可能获得一个内核对象的实际地址。Handle实际上是作为一个索引在一个表中查找对应的内核对象的实际地址。每个进程都有这样的一个表,叫句柄表。该表的第一项就是进程自己的句柄。Handle本质上就是一种用来"间接"代表一个内核对象的整数值,或者叫我们上面说的标识号。你可以在程序中使用handle来代表你想要操作的内核对象。这里的内核对象包括:事件、线程、进程、文件等。Handle仅在其所属的进程中才有意义。将一个进程拥有的handle传给另一个进程没有这个必要。
进程ID
进程ID是一个32位无符号整数,每个进程都有这样的一个ID,并且该ID在系统范围内是唯一的。系统使用该ID来唯一确定一个进程。
HINSTANCE
HINSTANCE也是一个32位无符号整数,它表示程序加载到内存中的基地址。Windows是基于虚拟内存的操作系统,所以Windows内存管理器经常在内存中来回移动对象,以此来满足各种应用程序的需要。对象一被移动地址就变化了。由于地址总是变化,所以Windows操作系统为各应用程序开辟出一个专门的内存地址,用来登记各对象在内存中的地址变化,而这地址本身是不变的。Windows内存管理器在移动对象在内存中的位置后,把对象新的地址往这里保存起来。这样我们只需记住这个句柄地址就可以间接地知道对象具体在内存中的什么位置。这个地址是在对象装载时由系统分配给的,当系统卸载时又释放给系统。
HWND是窗口相关的,你可以通过HWND找到该窗口所属进程和线程。Handle 是代表系统的内核对象,如文件句柄,线程句柄,进程句柄。系统对内核对象以链表的形式进行管理,载入到内存中的每一个内核对象都有一个线性地址,同时相对系统来说,在串列中有一个索引位置,这个索引位置就是内核对象的handle。HINSTANCE的本质是模块基地址,他仅仅在同一进程中才有意义,跨进程的HINSTANCE是没有意义。
再思考一下,那这个句柄为什么要被微软设计出来?原因何在?我总结了一下,原因如下:
适应虚拟内存
Windows是一个以虚拟内存为基础的操作系统,很多时候进程的代码和数据并不全部装入内存,进程的某一段装入内存后,还可能被换出到外存,当再次需要时,再装入内存。两次装入的地址绝大多数情况下是不一样的。也就是说,同一对象在内存中的地址会变化。那程序怎么才能准确地访问到对象呢?为了解决这个问题,Windows引入了句柄。补充一下,如果没有虚拟内存这个问题,所有的程序和内存的位置都是一一对应不会改变的,那似乎就没有发明句柄的必要了。我这里留个思考题给大家,既然这是虚拟内存造成的结果,那linux系统里有没有句柄?大家想一想。
32位操作系统中,系统为每个进程在内存中分配一定的区域,用来存放各个句柄,即一个个32位无符号整型值。每个32位无符号整型值相当于一个指针,指向内存中的另一个区域QY。而QY中存放的是对象在内存中的地址。当对象在内存中的位置发生变化时,QY的值被更新,变为当前对象在内存中的地址,而在这个过程中,QY的位置以及对应句柄的值是不发生变化的。也可以表述为:有一个固定的地址(句柄),指向一个固定的位置(QY),而QY中的值可以动态地变化,它记录着当前对象在内存中的地址。
这样,无论对象的位置在内存中如何变化,只要我们掌握了句柄的值,就可以找到QY,进而找到该对象。而句柄的值在程序本次运行期间是绝对不变的,操作系统是可以控制这个程序的,也就是抓住了程序的把柄。句柄是Windows中各个对象的一个唯一的、固定不变的ID,操作系统在启动后,会建立一个句柄表,程序在Windows系统上运行时会由操作系统给每个对象分配句柄,包括窗体句柄、文件句柄以及窗体上控件的句柄。Windows使用句柄来标识诸如窗口、位图、画笔等对象,并通过句柄找到这些对象。完整的链条关系如下:句柄地址(不变化)->记载着对象在内存中的地址->对象在内存中的地址(经常变化)->实际对象。
在程序的运行中,如果程序某一次运行完并关闭程序,后来再次启动程序运行,那么后来这次运行中,同一对象的句柄的值和上次运行时句柄的值一般是不一样的。程序每次重新启动,系统不能保证分配给这个程序的句柄还是原来的那个句柄,而且绝大多数情况的确是不一样的。句柄是对象生成时系统指定的,属性是只读的,程序员不能修改句柄。不同的系统中,句柄的大小(字节数)是不同的,可以使用sizeof()来计算句柄的大小。
适应内存管理
句柄的设计还解决了一个问题。操作系统有定期整理内存的计划任务,如果一些内存整理过一次后,对象找不到了怎么办?所以,Windows操作系统就采用这种设计理念,即指针的指针——句柄:在进程的地址空间中设一张表,表里头专门保存一些标识号(句柄)和由这个标识号(句柄)对应一个地址,再由那个地址去引用实际的对象,这个标识号(句柄)跟那个地址在数值上没有任何规律性的联系,只是映射。
提高调用效率
程序员只能通过句柄调用系统提供的服务(即API调用),但不能任意做超越权限的事,也就是不能像使用指针那样使用句柄做其他的操作,必须通过系统封装的API去使用。Windows系统中有许多内核对象,比如打开的文件,创建的线程,程序的窗口等等。这些对象因为复杂肯定不是4个字节或者8个字节足以完全描述的,他们拥有大量的属性。为保存这样一个"对象"的状态,往往需要上百甚至上千字节的内存空间,那么怎么在程序间或程序内部的子过程(函数)之间高效的传递这些数据呢?学过C语言的人会说可以传递这些对象的首地址呀,这确实是一个办法,你传一个地址效率高还是传一个窗口的所有数据效率高?显然是地址效率高。所以句柄的使用有效的提高了效率。
增加安全性
但这样做虽然会提高效率,却带来了安全风险,因为这样做会暴露了内核对象本身,使得其他程序因为知道了首地址,可以任意修改对象内部状态,这显然是操作系统内核所不允许的。所以句柄的使用增加了安全性。句柄和指针都是地址,不同之处在于句柄所指的可以是一个很复杂的结构,并且很有可能是与系统相关的,比如说线程的句柄,它指向的就是一个类或者结构,它和系统有很密切的关系。当一个线程由于不可预料的原因终止时,系统就可以返回这个线程所占用的的资料,如CPU ,内存等。句柄中的某一些项是与系统进行交互的。由于Windows系统是一个多任务的系统,它随时都可能要分配内存,回收内存,重组内存。指针也可以指向一个复杂的结构,但是通常是用户定义的,所以必须的工作都要用户完成。
总而言之,Windows程序中并不是用物理地址来标识一个内存块、文件或任务对象。Windows API给这些对象分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。在Windows编程中会用到大量的句柄。比如HINSTANCE(实例句柄),HBITMAP(位图句柄),HDC(设备表述句柄),HICON(图标句柄)等。
好了,看到这里,马上去看我的前一篇文章,你就能看明白句柄的意思了以及是怎么在程序上使用的。可点击下面链接。
帮你快速理解什么是MFC(Windows环境下)
各位小伙伴,这次我们就说到这里。下次我们再深入研究windows MFC和windows程序运行机制,相信你一定能喜欢上windows MFC。请关注我的这个系列的文章。文章来源:https://www.toymoban.com/news/detail-762292.html
作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。文章来源地址https://www.toymoban.com/news/detail-762292.html
到了这里,关于真正理解微软Windows程序运行机制——什么是句柄的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!