夜鹰教程网-程序员的加油站
 当前位置:文章中心 >> Python
Windows消息机制详解
夜鹰教程网 来源:www.yyjcw.com 日期:2017-10-22 13:19:45
Windows消息机制要点 

1. 窗口过程
     
每个窗口会有一个称为窗口过程的回调函数(WndProc),它带有四个参数,分别为:窗口句柄(Window Handle),消息ID(Message ID),和两个消息参数(wParam, lParam), 当窗口收到消息时系统就会调用此窗口过程来处理消息。(所以叫回调函数)

2 消息类型
     
     1) 系统定义消息(System-Defined Messages)
     
     在SDK中事先定义好的消息,非用户定义的,其范围在[0x0000, 0x03ff]之间, 可以分为以下三类:
     
     1> 窗口消息(Windows Message)
     
     与窗口的内部运作有关,如创建窗口,绘制窗口,销毁窗口等。可以是一般的窗口,也可以是Dialog,控件等。
     
     如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL...
     
     2> 命令消息(Command Message)
     
与处理用户请求有关, 如单击菜单项或工具栏或控件时, 就会产生命令消息。
     
     WM_COMMAND, LOWORD(wParam)表示菜单项,工具栏按钮或控件的ID。如果是控件, HIWORD(wParam)表示控件消息类型
     
     3> 控件通知(Notify Message)
     
     控件通知消息, 这是最灵活的消息格式, 其Message, wParam, lParam分别为:WM_NOTIFY, 控件ID,指向NMHDR的指针。NMHDR包含控件通知的内容, 可以任意扩展。
     
     2) 程序定义消息(Application-Defined Messages)
     
     用户自定义的消息, 对于其范围有如下规定:
     
     WM_USER: 0x0400-0x7FFF      (ex. WM_USER+10)
     
     WM_APP(winver> 4.0): 0x8000-0xBFFF (ex.WM_APP+4)
     
     RegisterWindowMessage: 0xC000-0xFFFF

3 消息队列(Message Queues)
     
     Windows中有两种类型的消息队列
     
     1) 系统消息队列(System Message Queue)
     
这是一个系统唯一的Queue,设备驱动(mouse, keyboard)会把操作输入转化成消息存在系统队列中,然后系统会把此消息放到目标窗口所在的线程的消息队列(thread-specific message queue)中等待处理
     
     2) 线程消息队列(Thread-specific Message Queue)
     
每一个GUI线程都会维护这样一个线程消息队列。(这个队列只有在线程调用GDI函数时才会创建,默认不创建)。然后线程消息队列中的消息会被送到相应的窗口过程(WndProc)处理.
     
     注意: 线程消息队列中WM_PAINT,WM_TIMER只有在Queue中没有其他消息的时候才会被处理,WM_PAINT消息还会被合并以提高效率。其他所有消息以先进先出(FIFO)的方式被处理。

4 队列消息(Queued Messages)和非队列消息(Non-Queued Messages)
     
     1)队列消息(Queued Messages)

     
     消息会先保存在消息队列中,消息循环会从此队列中取消息并分发到各窗口处理
     
     如鼠标,键盘消息。
     
     2) 非队列消息(NonQueued Messages)
     
消息会绕过系统消息队列和线程消息队列直接发送到窗口过程被处理
     
     如: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR, WM_WINDOWPOSCHANGED
     
     注意: postMessage发送的消息是队列消息,它会把消息Post到消息队列中; SendMessage发送的消息是非队列消息, 被直接送到窗口过程处理

5 PostMessage(PostThreadMessage), SendMessage
     
     PostMessage:把消息放到指定窗口所在的线程消息队列中后立即返回。 PostThreadMessage:把消息放到指定线程的消息队列中后立即返回。
     
     SendMessage:直接把消息送到窗口过程处理, 处理完了才返回。

6 GetMessage, PeekMessage
     
     PeekMessage会立即返回    可以保留消息
     
     GetMessage在有消息时返回  会删除消息

7 TranslateMessage, TranslateAccelerator
     
     TranslateMessage: 把一个virtual-key消息转化成字符消息(character message),并放到当前线程的消息队列中,消息循环下一次取出处理。
     
TranslateAccelerator: 将快捷键对应到相应的菜单命令。它会把WM_KEYDOWN 或 WM_SYSKEYDOWN转化成快捷键表中相应的WM_COMMAND 或WM_SYSCOMMAND消息, 然后把转化后的 WM_COMMAND或WM_SYSCOMMAND直接发送到窗口过程处理, 处理完后才会返回。

8(消息死锁( Message Deadlocks)
     
     假设有线程A和B, 现在有以下下步骤
     
     1) 线程A SendMessage给线程B, A等待消息在线程B中处理后返回
     
     2) 线程B收到了线程A发来的消息,并进行处理, 在处理过程中,B也向线程A SendMessgae,然后等待从A返回。
     
     因为此时, 线程A正等待从线程B返回, 无法处理B发来的消息, 从而导致了线程A,B相互等待, 形成死锁。多个线程也可以形成环形死锁。
     
     可以使用 SendNotifyMessage或SendMessageTimeout来避免出现死锁。

9 BroadcastSystemMessage
     
我们一般所接触到的消息都是发送给窗口的, 其实, 消息的接收者可以是多种多样的,它可以是应用程序(applications), 可安装驱动(installable drivers), 网络设备(network drivers), 系统级设备驱动(system-level device drivers)等,
     
     BroadcastSystemMessage这个API可以对以上系统组件发送消息。

 

一、引言
       
       随着Windows操作系统的不断推广,众多软件开发包都提供有开发基于Windows平台应用软件的功能。虽然这些开发包不尽相同,流行的有Visual C++、Visual Basic、Delphi、C++Builder 等多种,但由这些不同语言开发的软件有一点却是相同的--都是运行于Windows 操作平台,都必须接受Windows 的运行机制。作为Windows 操作系统灵魂的消息机制也就必然为众多用不同语言开发的Windows操作系统下运行的应用程序所接受。因此,要编写深入的Windows程序,就必须对 Windows的运行机制有很好的认识和理解。本文下面将对Windows操作系统下的消息运行机制做较为深入的剖析。
       
       二、Windows事件驱动机制
       
  我们当中不少使用VC、Delphi等作为开发语言的程序员是一步步从DOS下的Basic、C++中走过来的,而且大多在刚开始学习编程时也是先从 DOS下的编程环境入手的,因此在习惯了DOS下的过程驱动形式的顺序程序设计方法后,往往在向Windows下的开发环境转型的过程中会对 Windows所采取的事件驱动方式感到无法适应。因为DOS和Windows这两种操作系统的运行机制是截然不同的,DOS下的任何程序都是使用顺序 的、过程驱动的程序设计方法。这种程序都有一个明显的开始、明显的过程以及一个明显的结束,因此通过程序就能直接控制程序事件或过程的全部顺序。即使是在 处理异常时,处理过程也仍然是顺序的、过程驱动的结构。而Windows的驱动方式则是事件驱动的,即程序的流程不是由事件的顺序来控制,而是由事件的发 生来控制,所有的事件是无序的,所为一个程序员,在编写程序时,并不知道用户会先按下哪个按纽,也就不知道程序先触发哪个消息。因此我们的主要任务就是对 正在开发的应用程序要发出的或要接收的消息进行排序和管理。事件驱动程序设计是密切围绕消息的产生与处理而展开的,一条消息是关于发生的事件的消息。
       
       三、Windows的消息循环
       
  Windows操作系统为每一个正在运行的应用程序保持有一个消息队列。当有事件发生后,Windows并不是将这个激发事件直接送给应用程序,而是 先将其翻译成一个Windows消息,然后再把这个消息加入到这个应用程序的消息队列中去。应用程序需要通过消息循环来接收这些消息。在MFC中使用了对 WinAPI进行了很好封装的类库,虽然可以为编程提供一个面向对象的界面,使Windows程序员能够以面象对象的方式进行编程,把那些进行SDK编程 时最繁琐的部分提供给程序员,使之专注于功能的实现,但是由于引入了很好的封装特性,使我们不能直接操纵部分核心代码。对于消息的循环和接收也只是通过类 似于下面的消息映射予以很简单的表示:
       
       BEGIN_MESSAGE_MAP(CTEMMSView, CFormView)
       //{ { AFX_MSG_MAP(CTEMMSView)
       ON_WM_LBUTTONDOWN()
       ON_COMMAND(ID_OPENDATA, OnOpenData)
       ON_WM_TIMER()
       ON_WM_PAINT()
       //} } AFX_MSG_MAP
       END_MESSAGE_MAP()
       
  虽然上述消息映射在编程过程中处理消息非常简练方便,但显然是难于理解消息是如何参与循环和分发的。因此有必要通过SDK(Software Developers Kit,软件开发工具箱)代码深入到被MFC封装的Windows编程的核心中来研究其具体是如何工作的。在SDK编程中,一般是在Windows应用程 序的入口点WinMain函数中添加处理消息循环的代码以检索Windows送来的消息,然后WinMain再把这些消息分配给相应的窗口函数并处理它 们:
       
       ……
       MSG msg; //定义消息名
       while (GetMessage (& msg, NULL, 0, 0))
       {
       TranslateMessage (& msg) ; //翻译消息
       DispatchMessage (& msg) ; //撤去消息
       }
       return msg.wParam ;
       
       上述几句虽然简单但却是所有Windows程序的关键代码,担负着获取、解释和分发消息的任务,下面就重点对其功能和作用进行分析:
       
       MSG结构在头文件中定义如下:
       
       typedef struct tagMSG
       {
       HWND hwnd;
       UINT message;
       WPARAM wParam;
       LPARAM lParam;
       DWORD time;
       POINT pt;
       } MSG, *PMSG;
       
       其数据成员的具体意义如下:
       
       hwnd:消息将要发送到的那个窗口的句柄,用这个参数可以决定让哪个窗口接收消息。
       message:消息号,它唯一标识了一种消息类型。每种消息类型都在Windows文件进行了预定义。
       wParam:一个32位的消息参数,这个值的确切意义取决于消息本身。
       lParam:同上。
       time:消息放入消息队列中的时间,在这个域中写入的并非当时日期,而是从Windows启动后所测量的时间值。Windows用
       这个域来使用消息保持正确的顺序。
       pt:消息放入消息队列时的鼠标坐标。
       
  消息循环以GetMessage调用开始,它从消息队列中取出一个消息。该函数的四个参数可以有控制地获取消息,第一个参数指定要接收消息的MSG结 构的地址,第二个参数表示窗口句柄,一般将其设置为空,表示要获取该应用程序创建的所有窗口的消息;第三、四参数用于指定消息范围。后面三个参数被设置为 默认值,用于接收发送到属于这个应用程序的任何一个窗口的所有消息。在接收到除WM_QUIT之外的任何一个消息后,GetMessage()返回 TRUE;如果GetMessage收到一个WM_QUIT消息,则返回FALSE以退出消息循环,终止程序运行。因此,在接收到WM_QUIT之前,带 有GetMessage()的消息循环可以一直循环下去。当除WM_QUIT的消息用GetMessage读入后,首先要经过函数 TranslateMessage()对其进行解释,但对大多数消息来说并不起什么作用。这里起关键作用的是DispatchMessage()函数,把 由GetMessage获取的Windows消息传送给在MSG结构中为窗口所指定的窗口过程。在消息处理函数处理完消息之后,代码又循环到开始去接收另 一个消息,这样就完成了一个完整的消息循环。
       
  由于Windows操作系统是一种非剥夺式多任务操作系统。只有在应用程序主动交出CPU控制权后,Windows才能把控制权交给其他应用程序。在 消息循环中,一定要有能交出控制的系统函数才能实现协同式多任务操作。能完成该功能的只有GetMessage、PeekMessage和 WaitMessage这三个函数,如果在应用程序中长期不去调用这三个函数之一其他任务则无法执行。GetMessage函数在找不到等待应用程序处理 的消息时,会自动交出控制权,由Windows把CPU的控制权交给其他等待获取控制权的应用程序。由于任何Windows应用程序都含有一个消息循环, 这种隐式交出控制权的方式可以保证合并各个应用程序共享控制权。一旦发往该应用程序的消息到达应用程序队列,即开始执行GetMessage语句的下一条 语句。使用GetMessage函数的消息循环在消息队列中没有消息时将等待,如果需要,可以利用这段时间进行I/O端口操作等耗时操作,不过需要在消息 循环中使用PeekMessage函数来代替GetMessage。使用PeekMessage的方法同GetMessage类似,下面是一段使用 PeekMessage函数的消息循环的典型例子:
       
       MSG msg;
       BOOL bDone=FALSE;
       do{
       if(PeekMessage(& msg,NULL,0,0,PM_REMOVE)){
       if(msg.message==WM_QUIT)
       bDone=TRUE;
       else{
       TranslateMessage(& msg);
       DispatchMessage(& msg);
       }
       }
       //无消息处理,进行长时间操作
       else{
       ……//长时间操作
       }
       } while(!bDone)
       ……
       
       无论应用程序消息队列中是否有消息,PeekMessage函数都立即返回,如果希望等待新消息入队,可以利用无返回值的函数WaitMessage配合PeekMessage进行消息循环。
       
       四、对Windowds消息的处理
       
       窗口过程处理消息通常以switch语句开始,对于它要处理的每一条消息ID都跟有一条case语句,这在功能上同MFC的消息映射有些类似:
       
       switch(uMsgId)
       {
       case WM_TIMER:
       //对WM_TIMER定时器消息的处理过程
       return 0;
       case WM_LBUTTONDOWN:
       //对WM_ LBUTTONDOWN鼠标左键单击消息的处理过程
       ruturn 0;
       ……
       default:
       //其他消息由这个默认处理函数来处理
       return DefWindowProc(hwnd,uMsgId,wParam,lParam);
       }
       
  在处理完消息后必须返回0,这很重要,否则Windows将要不停地重试下去。对于那些在程序中不准备处理的消息,窗口过程会把它们都扔给 DefWindowProc进行缺省处理,而且还要返回那个函数的返回值。在消息传递层次中,可以认为DefWindowProc函数是最顶层的函数。该 函数发出WM_SYSCOMMAND消息,由系统执行Windows环境中多数窗口所公用的各种通用操作,如更新窗口的正文标题等等。 在MFC下可以用下述部分代码实现与上述SDK代码相同的功能:
       
       BEGIN_MESSAGE_MAP(CTEMMSView, CFormView)
       //{ { AFX_MSG_MAP(CTEMMSView)
       ON_WM_LBUTTONDOWN()
       ON_WM_TIMER()
       //} } AFX_MSG_MAP
       END_MESSAGE_MAP()
       
  小结:Windows环境提供有非常丰富的系统资源,在这个基础上可以编制出能满足各种各样目标功能的应用系统。要深入Windows编程就必须首先 对Windows系统的运行机理有很好的认识,本文仅针对Windows的一种重要运行机制--消息机制作了较深入的剖析和阐述。对培养在Windows 下的编程思想有一定的帮助。对某些相关问题的详细论述可以参考MSDN在线帮助的" SDK Reference" 部分。

复制链接 网友评论 收藏本文 关闭此页
上一条: 使用selenium爬取商城数据代码  下一条: SQLite在VC下的使用
夜鹰教程网成立于2008年,目前已经运营了将近 13 年,发布了大量关于 html5/css3/C#/asp.net/java/python/nodejs/mongodb/sql server/android/javascript/mysql/mvc/easyui/vue/echarts原创教程。 我们一直都在坚持的是:认证负责、一丝不苟、以工匠的精神来打磨每一套教程,让读者感受到作者的用心。我们默默投入的时间,确保每一套教程都是一件作品,而不是呆板的文字和视频! 目前我们推出在线辅导班试运营,模式为一对一辅导,教学工具为QQ。我们的辅导学科包括 java 、android原生开发、webapp开发、商城开发、C#和asp.net开发,winform和物联网开发、web前端开发,但不仅限于此。 普通班针对的是国内学员,例如想打好基础的大学生、想转行的有志青年、想深入学习的程序员、想开发软件的初学者或者业余爱好者等。 就业办针对即将毕业上岗的大四学生,或者打算转行的初级开发工程师。 留学生班针对的是在欧美、加拿大、澳洲、日本、韩国、新加坡等地留学的中国学子,目的是让大家熟练地掌握编程技能,按时完成老师布置的作业,并能顺利地通过考试。 详细咨询QQ:1416759661   夜鹰教程网  基于角色的权限管理系统(c-s/b-s)。
  夜鹰教程网  基于nodejs的聊天室开发视频教程
  夜鹰教程网  Git分布式版本管理视频教程
  夜鹰教程网  MVC+EasyUI视频教程
  夜鹰教程网  在线考试系统视频教程
  夜鹰教程网  MongoDB视频教程。
  夜鹰教程网 Canvas视频教程
  夜鹰教程网 报表开发视频教程
  热点推荐
C++从零开始学编程
C++ 数组array与vector的比较
VC、VC++、C++、C它们有什么区别
SQLite在VC下的使用
五种区别C++内存分配的方法
用Cint解析C++脚本
C打开文件的追加参数
C++ Builder 访问 USB 口的方法
C++中强制转换函数总结
为C++脚本加入新元素
C/C++编程中无所不在的指针概念
C++程序调用数据库的方法
内存泄漏是编程中常常见到的一个问…
C++中指针入门的最好的文章
C++编译错误解决方法
  最近更新
Python操作EXCEL表格
Python 优缺点
使用selenium爬取商城数据代码
Windows消息机制详解
SQLite在VC下的使用
五种区别C++内存分配的方法
VC的dll进行profile
C打开文件的追加参数
C/C++编程中无所不在的指针概念
C++从零开始学编程
VC、VC++、C++、C它们有什么区别
为C++脚本加入新元素
用Cint解析C++脚本
C++ Builder 访问 USB 口的方法
C++中强制转换函数总结

关于我们 | 网站建设 | 技术辅导 | 常见问题 | 联系我们 | 友情链接

夜鹰教程网 版权所有 www.yyjcw.com All rights reserved 备案号:蜀ICP备08011740号3