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. 安装v2ra y

    说明 全文中所有的‘删掉我’,都请忽略,如果不这么做,那么您将看不到这篇文章 ssr已经不行了,现在大家都以v2ra y为主 安装服务端 运行一键安装脚本,之后进入脚本界面,选“1”安装: bash ...

  2. 情感交流篇:HTML页面如何与后端联系

    通过ajax get方法: 基本格式:$.get("后台一般处理程序文件路径",{传值,格式为  KEY:VULES},function(后台返回值){接到后台数据后处理}); $ ...

  3. 22-MySQL DBA笔记-其他产品的选择

    第22章 其他产品的选择 本章将为读者介绍其他的数据库产品,主要是NoSQL产品的选择.读者在熟悉MySQL之外,也应该了解其他的数据库产品.本章的目的是给读者一个引导,如何选择一些NoSQL产品,而 ...

  4. (一)mybatis介绍

    一.mybatis简介 MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis ...

  5. IIs发布的项目无法打开问题

    1/查看一下ISAPI筛选器,是否存在2.0,4.0,若缺少东西,就从新安装一下iis,存在某些程序没有被勾选,一般是asp.net3.5,asp.net4.0

  6. Java Web 深入分析(7) Jetty原理解析

    1Jetty的基本架构 Jetty有一个基本的数据模型,这个模式就是handle,所有拷贝拓展的组件都被当做一个handler被添加到server中,然后由jetty统一管理. 1.1Jetty基本架 ...

  7. Go part 5 结构体,方法与接收器

    结构体 结构体定义 结构体的定义只是一种内存布局的描述(相当于是一个模板),只有当结构体实例化时,才会真正分配内存空间 结构体是一种复合的基本类型,通过关键字 type 定义为 自定义 类型后,使结构 ...

  8. Python练习_数据类型_day4

    题目 1.作业 1,写代码,有如下列表,按照要求实现每一个功能 li = ["alex", "WuSir", "ritian", " ...

  9. Python 生成动态变量 调用动态变量

    动态生成变量: variable = locals() for i in range(10): variable['A'+str(i)] = 123 print(A8) 调用动态变量: v = loc ...

  10. CSS伸缩布局

    1. 伸缩布局应用: 伸缩布局应用 主轴: Flex容器的主轴用来配置Flex项目,默认是水平方向 侧轴: 与主轴垂直的轴称为侧轴,默认还是垂直方向 方向: 默认是主轴从左向右, 侧轴默认是从上到下 ...