Windows以消息驱动的方式,使得线程能够通过处理消息来响应外界。

Windows 为每个需要接受消息和处理消息的线程建立消息队列(包括发送消息队列,登记消息队列,输入消息队列,响应消息队列),其中发送消息队列保存其他线程通过SendMessage发送给该线程建立窗口的消息,登记消息队列保存通过PostMessage发送给该线程或者该线程建立窗口的消息,输入消息队列保存系统的输入(包括键盘,鼠标输入),响应消息队列包含该线程调用SendMessage给指定窗口的窗口函数处理完后通知该线程的信息。

线程和消息队列关系:

RIT+SHIQ构成了系统的硬件输入模型的核心

系统为每个线程维护一个消息队列,还维护一个全局的消息队列,称为系统硬件输入队列(SHIQ:SystemHanrwareInputQueue),用于存储系统中硬件出发的消息。(如鼠标、键盘等引发的消息)在系统初始化的时候会建立一个特殊的线程------原始输入线程(RIT:RawInputThread).

系统为线程建立消息队列,实际上就是分配一个THREADINFO结构的数据,使其与线程关联。

在THREADINFO结构中包含有登记消息队列的指针、虚拟输入队列指针、发送队列指针、应答消息队列指针、退出代码、唤醒标记和局部输入状态变量等信息。

*   虚拟输入队列指针(Virtualized-input):接收接盘的等虚拟输入信息队列                                (就是一个指针数组)

    *   登记消息队列指针(Posted-Message):使用PostMessage函数发送的消息,将存放于此                      (就是一个指针数组)

    *   发送消息队列指针(Send-Message):SendMessage函数发送的消息存放位置                                (就是一个指针数组)

    *   应答消息队列指针(Reply-Message):使用SendMessage函数发送信息后,返回的信息存放于此

    *   nExitCode:确定线程退出状态,是一个int型,不同数值说明线程处于不同状态

    *   唤醒标志:判断是否处于唤醒状态

    *   局部输入状态变量:不详

PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam):(SendMessage和PostMessage的原理差不多,只是SendMessage会等待窗口过程处理完了以后才返回)

在调用该函数时,系统首先确定是哪个线程创建了hWnd参数标识的窗口,然后系统分配一块内存区域,将消息信息(该函数的实参)存储在这块区域中,并将该区域的首地址添加到线程的登记消息队列中。

 

整体流程:(以鼠标消息为例WM_MOUSEMOVE)

1.用户移动鼠标产生事件,系统通过设备驱动程序将消息(系统先将鼠标事件封装成消息,即MSG结构体)放入SHIQ(就是把一个指向MSG结构体的32位地址放入虚拟输入队列指针中),此时RIT会唤醒,RIT通过当前鼠标光标之下的窗口,获取创建窗口的线程ID(通过GetWindowThreadProcessId函数实现),然后将鼠标消息放入到线程的虚拟输入队列中(就是把一个指向MSG结构体的32位地址放入虚拟输入队列指针中)

2.应用程序从虚拟输入队列指针中取消息,并将其回传给操作系统

3.由操作系统调用窗口过程(通过hWnd找到所属的窗口类,在窗口类中找到窗口过程的地址)

伪算法:

Windows通过QS_SENDMESSAGE、QS_POSTMESSAGE、QS_QUIT、QS_INPUT、QS_PAINT、QS_TIMER表示是否有发送消息、登记消息、退出消息、输入消息、重绘消息、定时消息。

消息的优先级是QS_SENDMESSAGE > QS_POSTMESSAGE > QS_QUIT > QS_INPUT > QS_PAINT > QS_TIMER。

Windows处理消息的方式大概是这样的:

消息循环伪算法:

BOOL bRet = FALSE;
MSG msg;
while ((bRet = GetMessage(&msg, NULL, 0, 0))) {
         if (bRet == -1) break; // On Error exit the loop
         TranslateMessage(&msg); //转换消息
         DispatchMessage(&msg); //发送消息,其实就是调用指定窗口的窗口函数
}
 
GetMessage伪算法如下:
BOOL GetMessage(MSG *lpMsg, HWND hWnd , UINT wMsgFilterMin, UINT wMsgFilterMax)
{
         //查看QS_SENDMESSAGE标志,如果有的话循环处理,直到没有消息位置
         DWORD dwRetVal = 0;
         ThreadInfo threadInfo;
 
FLAG_SENDPROCLOOP:
         GetThreadInfo(GetCurrentThreadId(), &threadInfo);
         while (threadInfo.QS_SENDMESSAGE == QS_SIGNALSET) {
                   //从发送消息队列中获取消息
                   dwReturnVal = GetMsgFromQueue(QUEUE_SEND, lpMsg, hWnd,wMsgFilterMin, wMsgFilterMax);
                   //判断是否取到消息,有则调用窗口函数,无则复为QS_SENDMESSAGE标志
                   If (dwReturnVal == GETMESSAGE_HASMESSAGE) {
                            //调用指定窗口的窗口函数
                            CallWindowProc(hWnd, &threadInfo, lpMsg);
                   }
                   else {
                            QS_SENDMESSAGE = QS_SIGNALRESET;
                            break;
                   }
         }
         //在继续处理之前再次检查发送消息队列
         if (threadInfo.QS_SENDMESSAGE == QS_SIGNALSET) goto FLAG_SENDPROCLOOP;
         //检查发送消息队列, 如果有消息则取发送消息
         //判断是否还有发送消息,没有了则复位QS_POSTMESSAGE标志
         if (threadInfo.QS_POSTMESSAGE == QS_SIGNALSET) {
                   dwReturnVal = GetMsgFromQueue(QUEUE_POST, lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax);
                   if (dwReturnVal == GETMESSAGE_LASTMESSAGE)
                            threadInfo.QS_POSTMESSAGE = QS_SIGNALRESET;
                  
                   return TRUE;
         }       
 
         //如果退出标志被置位
         if (threadInfo.QS_QUIT == QS_SIGNALSET) {
                   threadInfo.QS_QUIT = QS_SIGNALRESET;
                   FillMessage(lpMsg, MESSAGE_QUIT);
                   return FALSE;
         }
 
         //检查输入消息队列
         if (threadInfo.QS_INPUT == QS_SIGNALSET) {
                   DWORD dwRetVal = GetMessageFromQueue(QUEUE_INPUT, lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax);
                   //检查是否有键盘,鼠标消息
                   if (Test(dwRetVal, QS_KEY) == QS_LASTMOUSEKEYMESSAGE)
                            threadInfo.QS_KEY = QS_SIGNALRESET;
                   if (Test(dwRetVal, QS_MOUSEBUTTON) == QS_LASTMOUSEMESSAGE)
                            threadInfo.QS_MOUSEBUTTON = QS_SIGNALRESET;
 
                   return TRUE;
         }
 
         //测试QS_PAINT
         if (threadInfo.QS_PAINT == QS_SIGNALSET) {
                   //填充MSG,如果没有窗口过程确认窗口,则复位QS_PAINT标志
                   //...
                   //返回TRUE
                   threadInfo.QS_PAINT = QS_SIGNALRESET;
                   return TRUE;
         }
 
         if (threadInfo.QS_TIMER == QS_SIGNALSET) {
                   //填充MSG,如果没有定时器报时,则复位QS_TIMER标志
                   //...
                   //返回TRUE
                   return TRUE;
         }
 
         //等待有消息到达
         dwRetVal = MsgWaitForMultipleObjectsEx(...);
         if (...)
                   goto FLAG_SENDPROCLOOP;
 
         //等待失败
         return FALSE;
}

上面要注意的是各种消息被处理的优先级顺序,在发送队列中有发送消息时,GetMessage不返回,直到将发送队列中消息处理完毕为止,然后复位QS_SENDMESSAGE,没有发送消息时,GetMessage才查看登记消息,如果没有登记消息,则依着优先级从高到低的顺序依次处理各种消息。 如果此过程中发现了优先级低的消息,则GetMessage填充一个MSG,然后返回。如果是QS_QUIT被置位,则GetMessage返回FALSE,否则返回TRUE。 当GetMessage返回FALSE时,消息循环也就结束了。看消息循环可知,当消息循环再次调用GetMessage时,依然按照优先级顺序依次处理各种消息。请注意SendMessage发送到目标线程消息队列的消息在目标线程调用GetMessage时被处理掉,直到没有发送消息为止GetMessage才回去查询其他消息,如果有消息GetMessage取到消息返回,否则GetMessage使得线程陷入IDLE状态,被挂起,当有消息到达线程时GetMessage被唤醒,获取消息返回。

Windows 消息以及消息处理算法--线程和消息队列详解的更多相关文章

  1. Java自带线程池和队列详解

    Java线程池使用说明 一简介 线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的.在jdk1.5之后这一情况有了很大的改观.Jdk1.5之后 ...

  2. Python算法应用实战之队列详解

    队列是一种先进先出(First-In-First-Out,FIFO)的数据结构.队列被用在很多地方,比如提交操作系统执行的一系列进程.打印任务池等,一些仿真系统用队列来模拟银行或杂货店里排队的顾客.下 ...

  3. Python实现的数据结构与算法之双端队列详解

    一.概述 双端队列(deque,全名double-ended queue)是一种具有队列和栈性质的线性数据结构.双端队列也拥有两端:队首(front).队尾(rear),但与队列不同的是,插入操作在两 ...

  4. 线程组ThreadGroup分析详解 多线程中篇(三)

    线程组,顾名思义,就是线程的组,逻辑类似项目组,用于管理项目成员,线程组就是用来管理线程. 每个线程都会有一个线程组,如果没有设置将会有些默认的初始化设置 而在java中线程组则是使用类ThreadG ...

  5. “全栈2019”Java多线程第二十二章:饥饿线程(Starvation)详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  6. “全栈2019”Java多线程第九章:判断线程是否存活isAlive()详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  7. “全栈2019”Java多线程第七章:等待线程死亡join()方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  8. “全栈2019”Java多线程第五章:线程睡眠sleep()方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  9. 线程池底层原理详解与源码分析(补充部分---ScheduledThreadPoolExecutor类分析)

    [1]前言 本篇幅是对 线程池底层原理详解与源码分析  的补充,默认你已经看完了上一篇对ThreadPoolExecutor类有了足够的了解. [2]ScheduledThreadPoolExecut ...

随机推荐

  1. (一)JMS简介

    一.简介 JMS即Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进 ...

  2. (错误)启动ActiveMQ报错:Transport Connector could not be registered in JMX: java.io.IOException: Failed to bind to server socket: stomp://0.0.0.0:61613?

    一.错误报告 很明显,端口被占用 二.解决方法 1. 在cmd中输入 netstat -ano 查看61613端口被占用情况,如果有其他进程使用,则使用 taskkill /f /pid 进程PID  ...

  3. (三)Activiti之第一个程序以及Activiti插件的使用和Activiti表的解释

    一.案例 1.1 建立Activiti Diagram图 new -> activiti ->Activiti Diagram,创建一个HelloWorld文件,后缀自动为bpmn,如下图 ...

  4. 利用jwt生成token,用于http请求身份验证

    前段时间在做移动端接口过程中,考虑到安全性,所有移动端发送请求(除了登录请求)过程中进行token有效验证. 1.利用jwt生成token a.导入jwt相关包 <!-- jwt --> ...

  5. 奇妙的算法【7】-贪婪算法-dp

    问题1描述:[贪婪算法,Dijistra算法] ①有一只兔子要从一个N*N的二维矩阵方格中从上跳到下面: ②每次只能向左或向下,越过一个方格,跳到下一个方格中: ③被越过的方格中的数值,表示该兔子越过 ...

  6. 解析Illumina+PacBio组装策略

    解析Illumina+PacBio组装策略 (2016-12-08 13:21:58) 转载▼     基于Illumina和PacBio平台的“二加三”组装策略,巧妙的融合了PacBio平台超长读长 ...

  7. ASP.NET WEB应用程序(.network4.5)MVC Razor视图引擎2 Areas区域说明

    https://www.cnblogs.com/webapi/p/5976642.html Asp.Net MVC Areas区域说明   一般网站分为前台+会员后台+管理员后台,做过webform的 ...

  8. [学习笔记]pb_ds库

    前言 其实我很早开始就用pb_ds库了,用起来确实方便.但最近感觉还是对这个了解颇少,还是来补一下 话说有人会忘记头文件,其实这有个伎俩,找到电脑上的g++文件夹.Ubuntu应该在etc中,Wind ...

  9. Centos 在VM中设置静态ip

    cd /etc/sysconfig/network-scripts 然后代开第一个文件 一般是ifcfg-ens331)开始配置原来是这样的 修改/etc/sysconfig/network # Cr ...

  10. jQuery异步请求ajax()之complete参数详解

    请求完成后回调函数 (请求success 和 error之后均调用).这个回调函数得到2个参数:XMLHTTPRequest) 对象和一个描述请求状态的字符串("success", ...