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. diverta 2019 Programming Contest

    A:签到. #include<bits/stdc++.h> using namespace std; #define ll long long #define inf 1000000010 ...

  2. JS OOP 概述

    JS面向对象,大致内容 1.面向对象的基础 2.深入认识JS的函数 3.JS类的实现 4JS中共有成员,私有成员和静态成员 5.JS的反射 6.JS的继承 7.JS实现抽象类 8.JS事件设计模式 9 ...

  3. 解决IE8中select下拉列表文字上下不居中的问题

    对IE8及以下的浏览器设置padding属性,其他浏览器则设置line-height 属性

  4. elment 中tree组件展开所有和收缩所有

    upAll () { // 全部展开 遍历变成true let self = this; // 将没有转换成树的原数据 let treeList = this.sourceData; for (let ...

  5. sql复杂的子查询,横向合并结果集

    第一个查询的结果集 select * from( select c.msName,a.msId,c.msPrice, c.msPrice*COUNT(a.msId) as totalMoney,sum ...

  6. string+DP leetcode-4.最长回文子串

    5. Longest Palindromic Substring 题面 Given a string s, find the longest palindromic substring in s. Y ...

  7. vue拦截

    ```javascript import Vue from 'vue' import App from './App.vue' import router from './router' import ...

  8. 安装jdk配置环境变量后jps command not found

    配置Java环境变量的时候一般是 vi /etc/profile 然后按两个大写的G就会跑到最后一行去,然后配置写入下文: 这个时候你jps查看Java的进程会出现: 分析原因: 一般是配置之后,没有 ...

  9. Java学习第四天之标识符与关键字

    Java语言和其他编程语言一样,使用标识符作为变量.对象的名字,也提供了系列的关键字用以实现特别的功能. 一.分隔符 Java语言里的分号(;).花括号({}).方括号([]).圆括号(()).空格. ...

  10. 《数据结构与算法之美》 <04>链表(上):如何实现LRU缓存淘汰算法?

    今天我们来聊聊“链表(Linked list)”这个数据结构.学习链表有什么用呢?为了回答这个问题,我们先来讨论一个经典的链表应用场景,那就是 LRU 缓存淘汰算法. 缓存是一种提高数据读取性能的技术 ...