消息循环

理解消息循环

  为了编写任何即使是最简单的程序,了解windows程序的消息循环和整个消息发送结构是非常有必要的。既然我们已经尝试了一点消息处理的东西,我们应该对整个程序有更深入的理解,如果你没有理解消息是怎么发生的和它们运行的机制,那接下来的内容你会感到很蛋疼。

什么是消息?

  一条消息是一个整数值,如果你查阅你的头文件(这是个好的查阅API的工作惯例)你会发现像下面的东西:

 #define WM_INITDIALOG                   0x0110
#define WM_COMMAND 0x0111 #define WM_LBUTTONDOWN 0x0201

  还有很多类似的东西。在windows中消息几乎被运用在基本水平上的所有通信,如果你想要一个窗口控制(只是一个制定的窗口)做某些事情你需要发送给它一条消息;同样的如果另一个窗口想让你做某些事情,它会发送给你一条消息;如果某些像用户点击键盘、移动鼠标、点击按钮的事件发生,那么相应的消息会被系统发送到受影响的窗口,如果你是其中一个受影响的窗口,那么你需要处理消息并采取相应的行动。

  每条窗口消息都可能有两个参数,wParam和lParam,最初wParam是16位而lParam是32位,但是在win32中它们都是32位。不是每条消息都需要用到这些参数,用到时的用法也不同,举个例子,WM_CLOSE消息就都没有用到这两个参数,这时我们可以忽略这两个采纳数;而WM_COMMAND就同时用到了这两个参数,wParam包含了两个值,HIWORD(wParam)是一条通知消息(如果可用的话),LOWORD(wParam)是一个发送消息的控制或菜单id。

  lParam是控制发送消息或NULL(如果消息不是从控制中来的话)的HWND(窗口句柄)

  HIWORD()和LOWORD()被windows宏定义,这两个值都是windows中的宏定义,一个指32位(0xFFFF0000)的高16位(FFFF),一个指32位的低16位(0000);在win32中一个WORD表示的是16位,而DWORD(double word)才表示32位

  你可以通过PostMessage()或SendMessage()这两种方式发送消息。PostMessage()将消息插入到消息队列中然后马上返回,这意味着即使一次PostMessage()的调用被完成时,消息可能还没被处理;SendMessage()直接发送消息给窗口并且会不会立即返回,知道窗口完成了对消息的处理。如果我们想关闭一个窗口,我们可以通过PostMessage(hwnd, WM_CLOSE, 0, 0)发送一个WM_CLOSE的消息,这跟我们点击窗口右上方的X按钮有同样的效果,注意在WM_CLOSE消息的相应处理中,wParam和lParam都是0,这是因为刚才提到的在WM_CLOSE消息处理中没有用到这两个参数。

对话框

  一旦你开始使用对话框盒子,为了跟它们交流你将需要发送消息给控制。你可以通过使用GetDlgItem()通过使用ID先获取控制的句柄然后使用SendMessage(),或者你可以使用SendDlgItemMessage(),这个方法组合了上面的两个步骤。你给它一个窗口句柄和孩子ID将会得到一个孩子句柄,然后向他发送消息。SendDlgItemMessage()和类似的APIGetDlgItemText()将会作用在所有的窗口上,而不仅仅是对话框。

什么是消息队列?

  让我们试着想象当你忙着处理WM_PAINT的时候突然用户用键盘输入一堆东西,会发生什么事情?你绘图的时候被键盘打断应该抛弃吗?当然不是!很明显不管抛弃哪个都不是正确的做法,所以我们有了消息队列。新增POST过来的消息会被添加到消息队列中来,当消息被处理时消息就会从消息队列中被移除,这确保了你不会错过一条消息,如果你正在处理一条消息,那么其他消息就会在队列中等待直到你开始处理它们。

什么是一个消息环?

 while(GetMessage(&Msg, NULL, , ) > )
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}

1、在消息队列里,消息环调用GetMessage()方法,如果你的消息队列空了,那么你的程序

将会停止然后等待消息,相当于挂起的状态

2、当一个事件发生时,会导致一条消息加入到消息队列中(比如系统注册一个鼠标点击)

GetMessage()返回一个正值表明有一个消息要处理,并且复制给我们传递给它的MSG数

据结构的成员,方法也可能会返回0如果消息是WM_QUIT,如果是发生错误的话方法会

返回负数。

3、我们得到消息(在Msg变量中)然后把它传递给TranslateMessage(),这会产生一点额外

的处理,将虚拟的键盘点击翻译成字符消息,这个步骤实际上是可选的,如果不需要时

它不会发生。

4、一旦上面的步骤完成,我们把消息传递给DispatchMessage(),DispatchMessage()会做的

工作就是拿到消息,检查它是哪个窗口的然后找到对应窗口的消息处理程序,接着调用

消息处理程序,并且将窗口的句柄,消息,wParam和lParam四个参数传递给它。

5、在你的消息处理程序中,你会检查参数消息,然后对它做任何你想做的事情,如果你没

有处理指定的消息,那么默认它会调用DefWindowProc()方法,这个消息处理的默认操作,

通常意味着什么都不做。

6、一旦你已经完成了消息处理,你的消息处理程序会返回,DisPatchMessage()返回,然后

我们又回到了环开始的地方。

  这是窗口程序中一个非常重要的概念,你的消息处理程序不是神奇地由系统调用,实际上你是间接地通过DispatchMessage()调用。如果你想要的话,你可以在窗口句柄中使用GetWindowLong()直接调用窗口处理程序。

 while(GetMessage(&Msg, NULL, , ) > )
{
WNDPROC fWndProc = (WNDPROC)GetWindowLong(Msg.hwnd, GWL_WNDPROC);
fWndProc(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
}

  我试着用前面的代码,但是并没有正常工作,因为有各种各样的问题比如Unicode/Ansi编码翻译等,调用定时器回调方法不会占用,这可能会打破我们目前的所有但微不足道的程序,所以你可以尝试,但在实际的代码中不要真的这么做: )

  注意,我们使用GetWindowLong()来检索窗口相关的消息处理程序,为什么我们不直接调用WndProc()呢?嗯,我们的消息环是要对我们程序中所有窗口都适用的,包括有自己的窗口过程的按钮和列表框,所以我们应该保证调用了正确的窗口过程,因为多个窗口可以使用同一个窗口过程,所以第一个参数(窗口句柄)用来告诉窗口过程消息属于哪个窗口的。

  正如你所看到的,你的应用程序的大多数时间都花费在执行这个消息循环,你可以愉悦地发送消息给可以处理它们的快乐的窗口。但是当你想退出程序时你会怎么做?因为我们使用了一个while()循环,如果GetMessage()返回了false,循环将会结束并且到达我们WinMain()的终点接着退出程序。这正是PostQuitMessage()完成的东西,它把WM_QUIT消息放入队列中而不是返回一个正值,GetMessage()会填充Msg数据结构的内容然后返回0,在这个时候Msg的wParam成员就包含了你传递给PostQuitMessage()的值,这里你可以忽略它,从WinMain()的返回值将会使用在进程结束的退出代码中。

重点:GetMessgae()将会返回-1如果出现错误的话,确保记住这个,否则在某些时候会让你很蛋疼。。。即使GetMessage()被定义用来返回一个布尔值,它也能够返回TRUE或FALSE之外的值,因为BOOL使用UINT(unsigned int)来定义的。下面的代码例子可能可以运行,但是不会正确地处理一下条件:

     while(GetMessage(&Msg, NULL, , ))

     while(GetMessage(&Msg, NULL, , ) != )

     while(GetMessage(&Msg, NULL, , ) == TRUE)

  上面都是错的!就像我刚刚提到的,它可能跟我第一个教程里使用的差不多,只要GetMessage()没有失败它会工作地很好,即使你的代码是正确的,这个也不是正确的,但是在很多情况下这个代码是错误的不能正常工作,当GetMessage()发生失败的时候!这里我们郑重说明和纠正,请原谅我错过了一些要点。

 while(GetMessage(&Msg, NULL, , ) > )

  这个才是正确的!

  我希望现在你对窗口消息循环有更好的理解,如果还不是非常理解的话,别害怕,一旦你使用它们一段时间之后情况会好很多。

  PS.由于本人英文水平所限,只能翻译到这个程度了,有纰漏还望多多指出,附上本篇翻译的英文原版教程地址:http://www.winprog.org/tutorial/message_loop.html

Win32编程API 基础篇 -- 4.消息循环的更多相关文章

  1. Win32编程API 基础篇 -- 1.入门指南 根据英文教程翻译

    入门指南 本教程是关于什么的 本教程的目的是向你介绍使用win32 API编写程序的基础知识(和通用的写法).使用的语言是C,但大多数C++编译器也能成功编译,事实上,教程中的绝大多数内容都适用于任何 ...

  2. Win32编程API 基础篇 -- 3.消息处理 根据英文教程翻译

    消息处理 例子:窗口点击 好的,现在我们已经得到一个窗口了,但我们什么也做不了除了DefWindowProc()允许窗口大小被调整,最大最小化等...这不是很激动人心啊 在接下来的一小节中我将向你展示 ...

  3. Win32编程API 基础篇 -- 6.菜单和图标

    菜单和按钮 例子:菜单1 本小节仅仅向你展示如果向你的窗口中加入一个基本的菜单,通常你会用到一个提前制作好的菜单资源,这会是一份.rc文件并且会被编译链接进你的.exe可执行程序中.这是具体的流程做法 ...

  4. Win32编程API 基础篇 -- 2.一个简单的窗口 根据英文教程翻译

    一个简单的窗口 例子:简单的窗口 有时人们在IRC提问,”我应该怎样制作一个窗口”...嗯,这恐怕不是完全这么简单好回答!其实这并不难一旦你明白你在做什么,但在你得到一个可展示的窗口之前还有一些事情需 ...

  5. Win32编程API 基础篇 -- 5.使用资源

    使用资源 你可能想参考教程结尾的附近,为了获得跟VC++和BC++资源相关的信息. 在我们讲得更加深入之前,我将大致讲解一下资源的主题,这样在每个小节中我就不必再去重讲一遍了.在这一小节中,你不需要编 ...

  6. 【TCP/IP】之Java socket编程API基础

    Socket是Java网络编程的基础,深入学习socket对于了解tcp/ip网络通信协议很有帮助, 此文讲解Socket的基础编程.Socket用法:①.主要用在进程间,网络间通信. 文章目录如下: ...

  7. ASP.NET Web API 基础篇1

    ASP.NET Web API 直到我膝盖中了一箭[1]基础篇 无题 蓦然回首,那些年,我竟然一直很二. 小时候,读武侠小说的时候,看到那些猪脚,常常会产生一种代入感,幻想自己也会遭遇某种奇遇,遇到悬 ...

  8. (转)Android高性能编程(1)--基础篇

    关于专题     本专题将深入研究Android的高性能编程方面,其中涉及到的内容会有Android内存优化,算法优化,Android的界面优化,Android指令级优化,以及Android应用内存占 ...

  9. 基础篇-Windows消息机制

    1在介绍Windows 消息运行机制之前,首先介绍一下消息的概念: 消息(Message)指的就是Windows 操作系统发给应用程序的一个通告[5],它告诉应用程序某个特定的事件发生了.比如,用户单 ...

随机推荐

  1. 微信小程序setData的使用,通过[...]进行动态key赋值

    首先先介绍一下微信小程序Page.prototype.setData(Object data, Function callback)的讲解: setData函数用于将数据从逻辑层发送到视图层(异步), ...

  2. HDU 4691 后缀数组+RMQ

    思路: 求一发后缀数组,求个LCP 就好了 注意数字有可能不只一位 (样例2) //By SiriusRen #include <bits/stdc++.h> using namespac ...

  3. 配置JDK、tomcat及Java Web项目部署

    一.JDK的安装 (1)下载安装JDK: 这个就不用说了,直接官网下载jdk安装即可.http://www.oracle.com/technetwork/java/javaee/downloads/i ...

  4. vue-devtools是vue浏览器调试工具

    开vue官网在vue-生态系统-工具可以看到vue-devtools这个工具: vue-devtools是一款基于chrome游览器的插件,用于调试vue应用,这可以极大地提高我们的调试效率.接下来我 ...

  5. 微信关于网页授权access_token和普通access_token的区别

    微信官网网址:https://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html#.E9.99.84.EF.BC.9A.E6. ...

  6. 【alert(1) to win】不完全攻略

    alert(1) to win 一个练习XSS的平台,地址:https://alf.nu/alert1 Warmup 给出了一段JavaScript代码 function escape(s) { re ...

  7. 怎么忽略ESLint校验

    方法一: 打开eslint的配置文件.eslintrc.js rules: { // allow async-await 'generator-star-spacing': 'off', // all ...

  8. ThinkPHP---thinkphp模型(M)

    (1)配置数据库连接 数据库的连接配置可以在系统配置文件ThinkPHP/Conf/convention.php中找到 /* 数据库设置 */ 'DB_TYPE' => '', // 数据库类型 ...

  9. 【Hadoop】二、HDFS文件读写流程

    (二)HDFS数据流   作为一个文件系统,文件的读和写是最基本的需求,这一部分我们来了解客户端是如何与HDFS进行交互的,也就是客户端与HDFS,以及构成HDFS的两类节点(namenode和dat ...

  10. python3.x Day3 文件编码

    文件编码: 知识点不多,但及其重要,python2和python3处理机制还有不同点,需要注意. 首先: 编码.数据类型,完全不同的概念. 文件编码:可以遵循开发环境.可以自行设定. 变量值编码:py ...