真正理解微软Windows程序运行机制——窗口机制(第二部分)

这篇具有很好参考价值的文章主要介绍了真正理解微软Windows程序运行机制——窗口机制(第二部分)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

真正理解微软Windows程序运行机制——窗口机制(第二部分)

我是荔园微风,作为一名在IT界整整25年的老兵,今天说说Windows程序的运行机制。经常被问到MFC到底是一个什么技术,为了解释这个我之前还写过帖子,但是很多人还是不理解。其实这没什么,我在学生时代也被这个问题困绕过。而且那个时间学习资料没有那么丰富,网上也没有什么资料,周围也没有懂的人,那个时候理解MFC更困难。甚至在我看来,理解这个比理解人工神经网络更难。

我认为造成这种现象的根本原因就是没有搞清楚Windows程序的运行机制,因为不理解Windows程序的运行机制,所以给理解MFC带来了很大的困难。我决定带所有微软开发技术的初学者一起攻破这个问题,但是一篇文章肯定是讲不清楚的,我们要分好几章来说。需要你有足够的耐心,一起来吧。我们这次来搞清楚什么是Windows程序的窗口机制。

下面我们来创建一个窗口,并在该窗口中响应键盘及鼠标消息,程序实现的步骤我们分三个部分,分别用三篇文章来讲清楚。

第一部分我们先写出WinMain()函数的定义,然后创建一个窗口。第二部分我们再通过消息循环来处理消息,最后第三部分我们编写窗口过程函数。为什么最后要编写窗口过程函数,主要是为了处理传过来的消息,这个我们第三部分再说。先不要急。只有以上三个部分完成了,我们就真正能做到创建一个窗口,并在该窗口中响应键盘及鼠标消息,不然少一步都做不到。  

上一次我重点讲了第一部分,现在我们来说第二部分,就是消息循环。

在完成创建窗口、显示窗口、更新窗口的工作后,需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。要从消息队列中取出消息,我们需要调用GetMessage()函数,该函数的原型声明如下:

BOOL GetMessage(
  LPMSG lpMsg,
  HWND hWnd,
  UINT wMsgFilterMin,
  UINT wMsgFilterMax
);

第一个参数lpMsg。指向一个消息(MSG)结构体,GetMessage从线程的消息队列中取出的消息信息将保存在该结构体对象中。

第二个参数hWnd。指定接收属于哪一个窗口的消息。通常我们将其设置为NULL,用于接收属于调用线程的所有窗口的窗口消息。

第三个参数 wMsgFilterMin。指定要获取的消息的最小值,通常设置为0。

第四个参数 wMsgFilterMax。指定要获取的消息的最大值。

如果 wMsgFilterMin 和wMsgFilterMax都设置为0,则接收所有消息。GetMessage 函数接收到 WM_QUIT以外的消息均返回非零值。对于 WM_QUIT 消息,该函数返回0。如果出现了错误,该函数返回-1,例如,当参数 hWnd是无效的窗口句柄或lpMsg是无效的指针时,该函数返回-1。

通常我们编写的消息循环代码如下:

MSG msg;

while(GetMessage(&msg,NULL,0,0))
{
  TranslateMessage(&msg);
  DispatchMessage(&msg);
}

那作为消息处理的机制,只有有消息来,我们才要处理,没有消息了,就不处理,那这样就可以通过上面这个函数来完成这个工作。我们已经说了GetMessage i函数只有在接收到 WM_QUIT消息时,才返回0。此时while语句判断的条件为假,循环退出,程序才有可能结束运行。在没有接收到WM_QUIT消息时, Windows应用程序就通过这个while循环来保证程序始终处于运行状态。

TranslateMessage 函数用于将虚拟键消息转换为字符消息。字符消息被投递到调用线程的消息队列中,当下一次调用 GetMessage 函数时被取出。我们现在来具体说一下这个过程,当我们敲击键盘上的某个字符键时,系统将产生 WM_KEYDOWN 和 WM_KEYUP 消息。这两个消息的附加参数,也就是wParam和IParam,包含的是虚拟键代码和扫描码等信息,而我们在程序中往往需要得到某个字符的 ASCII 码,TranslateMessage 这个函数就可以将 WM_KEYDOWN 和 WMKEYUP消息的组合转换为一条 WM_CHAR消息,该消息的 wParam 附加参数包含了字符的ASCII码,并将转换后的新消息投递到调用线程的消息队列中。TranslateMessage函数并不会修改原有的消息,它只产生新的消息并投递到消息队列中。

DispatchMessage函数分派一个消息到窗口过程,由窗口过程函数对消息进行处理。DispachMessage 实际上是将消息回传给操作系统,由操作系统调用窗口过程函数对消息进行处理,也就是响应。

Windows应用程序的消息处理机制可以画出一个图来看,画法很多,目前各大论坛中,我认为这张图画得最好,如下。

真正理解微软Windows程序运行机制——窗口机制(第二部分)

我们把上图这个Windows应用程序的消息处理过程再详细说说,上图1是指操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中。上图2应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息。在取出消息后,应用程序可以对消息进行一些预处理,例如,放弃对某些消息的响应,或者调用TranslateMessage产生新的消息。上图3应用程序调用DispatchMessage,将总回传给操作系统。消息是由MSG结构体对象来表示的,其中就包含了接收消息的窗口的句柄。因此,DispatchMessage函数总能进行正确的传递。上图4系统利用 WNDCLASS结构体的 lpfn WndProe 成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理(即“系统给应用程序发送了消息”)。

大家不要把前面的内容忘记了,我来回顾一下:

消息队列:消息队列有两种,分为系统消息队列和应用程序消息队列。产生的消息首先由Windows系统捕获,放在系统消息队列,再拷贝到对应的应用程序消息队列。32/64位系统为每一个应用程序维护一个消息队列。

消息循环:系统为每个应用程序维护一个消息循环,消息循环会不断检索自身的消息队列。每有一个消息,就用GetMessage()取出消息。

GetMessage具有阻塞机制。当消息队列中没有消息时,程序非忙等,而是让权等待。当收到WM_QUIT时,GetMessage返回false,循环停止,同时应用程序终止。

消息处理:DispatchMessage()把取出来的消息分配给相应的窗口或线程,由窗口过程处理函数DefWindowProc()处理。

Windows的应用程序靠消息驱动来实现功能。而消息驱动靠消息机制来处理。消息机制就是由消息队列,消息循环,消息处理构成的。

那么,消息机制是如何运作的呢?

当用户运行一个应用程序,通过对鼠标的点击或键盘按键,产生一些特定事件。由于Windows一直监控着I/O设备,该事件首先会被翻译成消息,由系统捕获,存放于系统消息队列

经分析,Windows知道该消息应由那个应用程序处理,则拷贝到相应的应用程序消息队列。由于消息循环不断检索自身的消息队列,当发现应用程序消息队列里有消息,就用GetMessage()取出消息,封装成Msg()结构。如果该消息是由键盘按键产生的,用TranslateMessage()翻译为WM_CHAR消息,否则,用DisPatchMessage()将取出的消息分发到相应的应用程序窗口,交由窗口处理程序处理。

Windows为每个窗体预留了过程窗口函数,该函数是一个回调函数,由系统调用,应用程序不能调用。程序员可以通过重载该函数处理我们想要去处理的消息。对于不想处理的消息,则由系统默认的窗口过程处理程序做出处理。

目前各论坛里画的最好的一张图是这个,我上次也向大家推荐过:

真正理解微软Windows程序运行机制——窗口机制(第二部分)

 上图很好地解释了消息机制的运行原理,是我见过的画的最好的一幅。 

当运行程序->事件操作引发消息->消息先存在系统消息队列->再存入到应用程序消息队列->用消息循环提取消息->处理消息->再返回消息队列。


当然,从消息队列中获取消息还可以调用PeekMessage 函数,该函数的原型声明如下所示:

BOOL PeekMessage(
  LPNSG lpMsg,
  HWND hWnd,
  UINT wMsgFilterMin,
  UINT wMsgFilterMax,
  UINT wRemoveMsg
);

前4个参数和GetMessage函数的4个参数的作用相同。最后1个参数指定消息获取的方式,如果设为PM_NOREMOVE,那么消息将不会从消息队列中被移除;如果设为PM_REMOVE,那么消息将从消息队列中被移除(与GetMessage 函数的行为一致)。

发送消息可以使用 SendMessage 和 PostMessage函数。 SendMessage将消息直接发送给窗口,并调用该窗口的窗口过程进行处理。在窗口过程对消息处理完毕后,该函数才返回(SendMessage 发送的消息为不进队消息)。PostMessage函数将消息放入与创建窗口的线程相关联的消息队列后立即返回。除了这两个函数外,还有一个 PostThreadMessage 函数,用于向线程发送消息,对于线程消息,MSG结构体中的 hwnd成员为NULL。

现在我们来理一下,windows消息循环的详细过程

第一步,我们创建完win32应用程序,当用户通过对鼠标,键盘操作应用程序时,由于Windows一直监控着I/O设备,该事件首先会被转化成消息,由windows系统捕获,存放于系统消息队列。Windows系统知道该消息应由哪个应用程序处理,然后拷贝到相应的应用程序消息队列。同时将该消息从系统消息队列中删除。

第二步,应用程序的消息循环不断在执行,此时,调用GetMessage()从消息队列中查找消息,发现了该消息,GetMessage()将返回一个正值,并获取到了该消息Msg;如果消息队列为空,程序将停止执行并等待(程序阻塞)。


第三步, 然后取出消息(Msg)并将其传递给TranslateMessage()函数,这个函数做一些额外的处理:将虚拟键值信息转换为字符信息。这一步是可选的。


第四步,上面的步骤执行完后,将消息MSG传递给DispatchMessage()函数。DispatchMessage()函数将消息再给windows系统,由windows系统找到目标窗口并分发给该窗口,调用消息对应的窗口过程函数,既窗口的WinPro函数,让WinPro函数处理。WinPro函数可以允许我们对不同的消息做特定的处理,若不处理的话,则会调用DefWindowProc函数,做默认处理,所以为什么默认代码中WinPro的类型是CallBack(回调),因为不是我们主动调用的,是系统回调的。


第五步, 一旦一个消息处理完成,窗口过程WinPro函数返回,DispatchMessage()函数返回,应用程序的消息循环继续while循环,Window系统继续监控各类消息,重复上述步骤。

各位小伙伴,我们就说到这里,下次我们再深入研究windows程序的运行机制。

作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。
 

 文章来源地址https://www.toymoban.com/news/detail-410623.html

到了这里,关于真正理解微软Windows程序运行机制——窗口机制(第二部分)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 微软MFC技术运行机制

    我是荔园微风,作为一名在IT界整整25年的老兵,今天总结一下微软MFC技术运行机制。 很多初学者误以为VC++开发必须使用MFC,其实不一定的。MFC的使用只能是提高程序在某些情况下的开发效率,而不能替代整个Win32程序设计。我认为我们有必要再来好好讲讲MFC的本质、MFC中的

    2024年02月08日
    浏览(40)
  • 【MFC】10.MFC六大机制:RTTI(运行时类型识别),动态创建机制,窗口切分,子类化-笔记

    C++: ##是拼接 #是替换成字符串 这是MFC提供的运行时类信息的使用,如果我们自己创建一个类,如果想用这些方法,必须要满足三个条件: 这个类必须继承于CObject类 类内必须声明DECLARE_DYNAMIC 类外必须实现IMPLENENT_DYNAMIC 我们来看看是如何实现的: 这里给出RTTI的图,每一个类中

    2024年02月13日
    浏览(36)
  • windows窗口创建流程及window消息机制详解

    本文通过对windows窗口创建的流程来分析在windows系统中消息的产生、获取、处理的方法,详细介绍windows的消息机制,方便Windows开发者对windows的消息机制进行理解。 定义:窗口类是一个属性集,是Windows编程中用于创建窗口的模板。窗口类包含了窗口的各种信息的数据结构,每

    2024年02月05日
    浏览(76)
  • MFC 第二部分 : 窗口类成员接口

    所有窗口类的基类:类 CWnd,封装了 Windows 窗口句柄 HWND。 成员函数 DestroyWindow 可以消毁 Windows 窗口,而不需要消毁 CWnd 对象。 m_hWnd 与该 CWnd 对象相关联的 Windows 窗口句柄(HWND); 窗口大小和位置 GetWindowRgn 获得窗口的窗口区域的拷贝 SetWindowRgn 设置窗口区域 IsIconic 判断窗口是

    2024年02月06日
    浏览(38)
  • 【计算机网络】深入理解TCP协议二(连接管理机制、WAIT_TIME、滑动窗口、流量控制、拥塞控制)

    正常情况下,TCP需要经过三次握手建立连接+四次挥手断开链接,下面看一个图: 服务器的状态变化: [CLOSED - LISTEN] 服务器端调用listen后进入LISTEN状态, 等待客户端连接; [LISTEN - SYN_RCVD] 一旦监听到连接请求(同步报文段), 就将该连接放入内核等待队列中, 并向客户端发送SYN确认

    2024年02月07日
    浏览(57)
  • 深入理解Windows操作系统机制(二)

    我是荔园微风,作为一名在IT界整整25年的老兵,今天我们来重新审视一下Windows这个我们熟悉的不能再熟悉的系统。 我们每天都在用Windows操作系统,但是其实我们每天直接在打交道的并不是Windows操作系统的内核,而是Windows操作系统的人机交互界面,这个界面其实只是Window

    2024年02月17日
    浏览(51)
  • Windows bat隐藏运行窗口的几种方案

    有些程序在执行批处理脚本时,可能会看到dos窗口,或者看到窗口一闪而过。如果批处理脚本执行过程中不需要与用户进行交互,那么为了提升用户体验,防止用户误操作,关闭了正在运行的批处理dos窗口,而导致的一些问题,建议将批处理放到后台(隐藏)运行。 接下来讲

    2024年02月12日
    浏览(47)
  • “深入探索JVM内部机制:理解Java虚拟机的运行原理“

    标题:深入探索JVM内部机制:理解Java虚拟机的运行原理 摘要:本篇博客将深入探索Java虚拟机(JVM)的内部机制,帮助读者理解JVM的运行原理。我们将介绍JVM的组成结构,包括类加载器、运行时数据区域和执行引擎,并通过示例代码解释这些概念的具体应用。 正文: 一、J

    2024年02月11日
    浏览(45)
  • windows运行.bat文件且设置为开机启动,不显示窗口

    步骤1;为 脚本 设置桌面快捷方式 步骤2: 将生成的快捷方式复制/剪切到以下文件夹 C:ProgramDataMicrosoftWindowsStart MenuProgramsStartup 这样就能开机自动启动了,愉快使用吧! 创建vbs文件

    2024年02月12日
    浏览(82)
  • Win11/Windows11设置始终以管理员身份运行cmd窗口

    在使用Windows进行开发时,我们经常需要使用管理员身份运行cmd窗口, 但是每次打开都需要右键\\\"以管理员身份运行\\\",比较浪费时间, 下面将介绍在Win11/Windows11系统中,设置始终以管理员身份运行cmd窗口!         在搜索栏输入终端,点击打开。         如下图所示:  

    2024年02月12日
    浏览(50)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包