第28章 硬件输入模型和局部输入状态

28.1 原始输入线程(RIT)

(1)图解硬件输入模型

  ①当操作系统初始化时会创建一个原始输入线程(RIT)系统硬件消息队列(SHIQ),这两者是系统硬件输入模型的核心。当SHIQ队列有硬件(如鼠标或键盘)消息时,RIT被唤醒,并将事件添加到用户线程的VIQ队列。

  ②任何时刻,只能有一个用户线程与RIT连接,该线程被称为前景线程。相对于其他线程创建的窗口,前景线程创建的窗口主要当前正在与用户交互的窗口。

  ③RIT如何知道要往哪个线程增加硬件输入消息?

    A.如果是鼠标消息,RIT会调用检查当前鼠标所在的窗口,并调用GetWidowThreadProcessId找出创建该窗口的线程,然后向该线程添加消息。

    B.如果是键盘事件,RIT只向前景线程添加键盘消息

  ④RIT如何切换与不同线程的连接?

    A.当进程创建一个子进程,而子进程的线程里创建了一个窗口时,这个子进程的线程成为前景线程。

    B.RIT负责Alt+Tab、Alt+Esc和Ctrl+Alt+Del键的处理,用户可以通过这些按键的组合来激活窗口并将创建该窗口的线程连接到RIT。(注意这些按键组合在RIT内部处理,所以我们无法拦截或丢弃它们)。Windows提供了一些激活窗口的函数(见后面)

(2)硬件输入模型的健壮性

  ①假设当前前景线程为线程B,此时RIT将消息分派给线程B的VIQ队列。如果此时线程B遇到一个死循环或死锁。由于线程B仍与RIT相连,所以系统的硬件消息仍会被添加到线程B的队列中。

  ②当用户按住Alt+Tab键时,由于Alt+Tab组合键由RIT线程内部处理,所以用户可以激活其他线程的窗口(如窗口A1),这里线程A将连接到RIT。因此,保证了当线程B出了问题时,不会影响到其他线程的执行。

28.2 局部输入状态

  每个线程都有自己的THREADINFO结构,通过使用THREADINFO结构可以控制虚拟输入队列和一组状态信息,从而可以让线程使用虚拟输入队列和状态信息变量来实现对“窗口焦点、鼠标捕获”的管理信息有各自不同的处理,以防止对其他线程的影响。主要有两类:

  ①管理与键盘输入及焦点窗口有关的信息:如哪个窗口拥有键盘焦点、哪个窗口是活动的、键盘上哪些键被按下。

  ②管理与鼠标和光标有关的信息:哪个窗口捕获鼠标、鼠标的形状、鼠标的可见性

28.2.1 键盘输入与焦点

(1)焦点窗口与活动窗口的区别

  线程内部会维护当前自己的活动窗口(Active Window)和焦点窗口(Focus Window),焦点窗口其实只是窗口的一个属性,其实就是“焦点状态”是窗口的一个属性,而焦点窗口的顶层窗口就是活动窗口。比如,一个对话框中有一个按钮,当按钮获得焦点的时候,那此按钮就是焦点窗口,则包含此按钮的对话框就是活动窗口,若出现窗口嵌套的情况,则最根的那个窗口才是活动窗口。

  焦点窗口只是一个局部的概念,并不是所有的焦点窗口都可以获得键盘事件。只有前景线程的焦点窗口才能从系统队列中得到键盘事件(所以要SetFocus()),而前景线程中的活动窗口是前景窗口。在任何时刻系统中都只可能有一个被激活的窗口,这就是前景窗口。

(2)窗口间的焦点切换

  ①RIT是将键盘输入放到线程的虚拟输入队列(VIQ),而不是某个窗口。当线程GetMessage时,键盘事件被从VIQ中删除,并被分派到当前的焦点窗口

  ②要让不同窗口接受键盘输入,须做两件事:一是让将接受线程与RIT连接。二是在该线程局部输入状态变量中记录要成为焦点的窗口。而SetFocus不能同时完成这两件事,只能做后面的一件。

  ③假设当前线程1与RIT连接,当调用SetFocus,然后传入参数hwndA、hwndB、hwndC则焦点会在这三个窗口之间切换。但如果传入的是hwndD、hwndE、hwndF中的任何一个都会失败,实际上SetFocus什么事情都不做。因为当前线程B还没与RIT相连接。

  ④仍然假设线程1与RIT正连接着,如果线程2调用SetFocus,然后传入hwndE。此时,线程2的局部输入状态变量会被更新以反映这一变化,在以后线程2连接到RIT时,键盘事件会被分派到窗口E中。但要注意的是,虽然在线程2还未连接RIT之前,窗口E还不能接收到键盘消息,但它会收到WM_SETFOCUS消息,如果是按钮,其表面会绘出虚线框。

  ⑤当焦点切换时,失去焦点的窗口会收到WM_KILLFOCUS消息。如果接收焦点的窗口与失去窗口的焦点属于不同线程创建的,那么失去焦点的窗口会更新局部输入状态变量,以表明该线程现在没有焦点窗口(调用GetFocus会返回NULL)。

(3)设置焦点窗口函数的比较

  ①SetForegroundWindow 和 SetActiveWindow的区别

  SetActiveWindow(hWnd)函数,改变的是一个线程的局部状态变量,将活动窗口置为hWnd。如果当前线程是背景线程,则只改变局部状态变量。如果是前景线程,则该窗口会被置为前景窗口。但要注意的是这个函数不能够跨线程调用(也就是说不能够改变另外一个线程的局部变量),即如果hWnd为其他线程的窗口,则调用线程的局部状态变量将被设为NULL,所以GetActivWindow会返回NULL。

  SetWindowPos、BringWindowToTop(该函数内部调用了SetWindowPos)可以跨线程或进程调用,函数会改变窗口的Z序,激活状态和焦点。如果调用线程未连接到RIT则什么也不做如果调用线程己连接到RIT,则系统会激活hWnd窗口(其他线程也可以)。这也就意味着如果hWnd是其他线程创建的窗口,则会同时将这个线程连接到RIT,并改变其局部输入状态来反应激活窗口的变化。

  SetForegroundWindow将窗口设为前景窗口, Windows为了防止突然的一个窗口跳至屏幕的Foreground,所以如果调用线程是背景线程,则产生的将是任务栏闪烁效果,表示不当前前景进程(正连接RIT的进程)不允许该窗口置于它的前面而成为前景窗口,我们可以手动到任务栏去激活这个窗口。而BringWindowToTop和SetWindowPos (TOP)在没有连接到RIT的时候则干脆不起效果。但是需要注意的是SetWindowPos(BOTTOM)还是有效果的(因为不违反Windows的这个约束)。

  ②但我们调用AllowSetForegroundWindow(dwProcessId),表示允许dwProcessId进程弹出的窗口置于调用线程的窗口之上。当传入参数ASFW_ANY表示允许任何进程,如果这时调用线程为RIT,而其他线程要通过SetForegroundWindow来置顶窗口时,只会在任务栏中闪烁提示,而不会真正被置顶。

  ③LockSetForegroundWindow函数,如果调用线程调用该函数并传入LSFW_LOCK参数,则当该线程为前景线程时,任何其他线程调用SetForegroundWindow函数都将失败。这可以防止当前线程的前景窗口被其他线程的窗口给挡住。传入参数LSFW_UNLOCK时则解锁这种阻止。当用户按Alt键或用户显式将一个窗口变为前景窗口时,系统会自动解锁这种阻止,以防止一个应用程序总是霸占桌面。

(4)键盘状态——比较GetKeyState和GetAsyncKeyState函数


函数


描述


GetKeyState(int nVirtKey)


①线程局部输入状态包含一个同步键状态数组,这个数组为线程私有。

②获得最近那次从消息队列中删除键盘消息时的按键状态,是从线程私有的同步键状态数组中获取到的。

③nVirtKey指出要检查键的虚键代码,结果的高位为1时表示按下,0为释放


GetAsyncKeyState(int nVirtKey)


①该函数从异步键状态数组中获取按键的状态,这个数组是所有线程所共享的,函数检查当前实时的键盘状态。

②nVirtKey指出要检查键的虚键代码。结果的高位为1时表示按下,0为释放。

③如果调用线程不是当前焦点窗口的创建线程,则函数总是返回0.

28.2.2 鼠标光标管理

(1)ShowCursor(BOOL bShow):

  ①只影响调用线程的光标状态。

  ②调用ShowCursor(FALSE)多少次来隐藏,就要ShowCursor(TRUE)多少次才能显示出来。

(2)ClipCursor(CONST RECT* prc);

  将鼠标限制在prc指定的范围内。但当异步激活事件发生(如用户激活另一个窗口)、调用SetForegroundWindow或用户按了Ctrl+Esc时,系统会停止鼠标剪裁。

(3)SetCapture(HWND hWnd)

  ①当调用SetCapture时,RIT会将所有消息发送给调用线程的VIQ队列,并把所有消息都发送给hWnd窗口。同时设置局部输入状态,以反映是哪个窗口被捕获。调用ReleaseCapture将释放捕获

  ②当鼠标按住时调用SetCapture,这里进行的是系统范围内的捕获。不管鼠标移到桌面上的哪个位置,鼠标消息都被发往hWnd窗口。如果此时释放鼠标按键时,RIT会检测到这个动作,此时如果鼠标位于其他线程创建的窗口时,RIT会将鼠标消息发给鼠标光标之下的窗口,而不是hWnd。如果鼠标位于调用线程创建的任何窗口时,这里系统会认为鼠标捕获仍然有效,所以只要鼠标位于调用线程所创建的任何一个窗口中,鼠标消息都会被发往hWnd窗口。换一句话讲,如果用户释放鼠标时,鼠标捕获不再是系统全局的捕获,而是线程局部的一种捕获。

  ③如果用户试图去激活另一个线程的窗口时,系统会自动向调用SetCapture线程发送鼠标按下和释放的消息。然后系统会更新调用线程的局部输入状态,以将捕获窗口设为NULL。

(4)SetCursor(HCURSOR hCursor);——设置光标的形状

  ①改变光标形状,并设置线程局部输入状态变量以更新鼠标的形状信息

  ②当鼠标在窗口中移动时(前提是未设置鼠标捕获),窗口会收到WM_SETFOCUS消息,这时可以调用SetCursor函数来设置鼠标的形状。

28.3 让多个线程共享某个线程的虚拟输入队列和局部输入状态变量

(1) AttachingThreadInput(IdAttach,IdAttachTo,fAttach);


参数


说明


IdAttach


参数为不再使用虚拟输入队列和局部输入变量的线程Id


IdAttachTo


参数要共享虚拟输入队列和局部输入变量的线程Id


fAttach


TRUE时表示要挂接线程以共享,FALSE表示分离线程的VIQ和局部输入变量。


备注:可多次调用,以让多个线程共享同一个VIQ和局部输入状态变量

(2)举例说明:AttachThreadInput(idThreadA,idThreadB,TRUE);

  ①所有将输入窗口A1、B1、B2的硬件输入事件都将被添加到线程B的虚拟输入队列。在分离之前,线程A的虚拟输入队列不再接收输入事件。

  ②当两个线程共享虚拟输入队列时,也会共享同一套局部输入状态变量。但是会使用各自的posted-message、send-message、reply-message队列及唤醒标志位

  ③当线程共享单一个VIQ队列时,会严重影响程序的健壮性。当一个线程接收一个击键消息并挂起,另一个线程就不能接收任何的输入。

(3)使用AttachThreadInput的场合

  ①在少数场合下,系统会显式地将两个线程挂起在一起。如线程安装了日志记录或日志回放钩子。在钩子卸载时会自动将两个线程分离。如果线程安装日志记录钩子,它等于告诉系统当发生硬件输入事件时,它都应该被通知。由于用户的输入必须被按相同的顺序记录下来,所以系统会共享一个VIQ以让所有的输入处理都能被同步起来。

  ②当应用程序创建了两个线程,第1个线程创建了一个对话框,当创建完成后。第2个线程调用CreateWindow,使用WS_CHILD并将对话框的句柄传给函数以便创建一个对话框的子窗口。系统会调用AttachThreadInput让第1个线程与第2个线程共对话框线程的VIQ,这个动作可以强制所有对话框所有的子窗口(包括第1个线程创建和其他线程创建的窗口)输入都可以同步起来。

时间: 2024-10-06 13:25:43

第28章 硬件输入模型和局部输入状态的相关文章

第12章 GPIO输入-按键检测—零死角玩转STM32-F429系列

第12章 ????GPIO输入-按键检测 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/firege ? 本章参考资料:<STM32F4xx参考手册>.库帮助文档<stm32f4xx_dsp_stdperiph_lib_um.chm>. 按键检测使用到GPIO外设的基本输入功能,本章中不再赘述GPIO外设的概念,如您忘记了,可重读前面"GPIO框图剖析"小

第12章 GPIO输入—按键检测

第12章     GPIO输入-按键检测 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/firege 本章参考资料:<STM32F4xx参考手册>.库帮助文档<stm32f4xx_dsp_stdperiph_lib_um.chm>. 按键检测使用到GPIO外设的基本输入功能,本章中不再赘述GPIO外设的概念,如您忘记了,可重读前面"GPIO框图剖析"小节,

第九章: 输入与输出

@font-face{ font-family:"Times New Roman"; } @font-face{ font-family:"宋体"; } @font-face{ font-family:"Arial"; } @font-face{ font-family:"黑体"; } @font-face{ font-family:"微软雅黑"; } p.MsoNormal{ mso-style-name

[书籍翻译] 《JavaScript并发编程》 第二章 JavaScript运行模型

本文是我翻译<JavaScript Concurrency>书籍的第二章 JavaScript运行模型,该书主要以Promises.Generator.Web workers等技术来讲解JavaScript并发编程方面的实践. 完整书籍翻译地址:https://github.com/yzsunlei/javascript_concurrency_translation .由于能力有限,肯定存在翻译不清楚甚至翻译错误的地方,欢迎朋友们提issue指出,感谢. 本书第一章我们探讨了JavaScri

【WPF学习】第三十一章 WPF命令模型

原文:[WPF学习]第三十一章 WPF命令模型 WPF命令模型由许多可变的部分组成.总之,它们都具有如下4个重要元素: 命令:命令表示应用程序任务,并且跟踪任务是否能够被执行.然而,命令实际上不包含执行应用程序任务的代码. 命令绑定:每个命令绑定针对用户界面的具体区域,将命令连接到相关的应用程序逻辑.这种分解的设计是非常重要的,因为单个命令可用于应用程序中的多个地方,并且在每个地方具有不同的意义.为处理这一问题,需要将同一命令与不同的命令绑定. 命令源:命令源触发命令.例如,MenuItem和B

第16章 CSS盒模型下

第 16章 CSS盒模型[下]学习要点:1.元素可见性2.元素盒类型3.元素的浮动 本章主要探讨 HTML5中 CSS盒模型,学习怎样了解元素的外观配置以及文档的整体布局. 一.元素可见性使用visibility属性可以实现元素的可见性,这种样式一般可以配合 JavaScript来实现效果.样式表如下:属性 visibility 值 说明 CSS版本visible 默认值,元素在页面上可见 2hidden 元素不可见,但会占据空间. 2collapse 元素不可见,隐藏表格的行与列. 2 如果不

C和指针 (pointers on C)——第十五章:输入输出函数

第十五章 输入输出函数 这一章读完的第一感觉就是"呵呵". 如果说上过C语言课,基本上scanf()/printf()算是用的比较熟练了.尤其是那些抽象的格式说明.还有scanf()为什么要加括号. 读过本书前面的内容的话,getchar(),putchar(),gets(),puts()这些应该也问题不大. 再如果的话,你学过计算机图形学,你玩过OpenGL,听说过双缓存机制,那么fflush()也肯定弄明白了. 再加上FILE的操作,输入输出定位刷新删除改名,流的概念. 这一章就会

《Java并发编程实战》第十六章 Java内存模型 读书笔记

Java内存模型是保障多线程安全的根基,这里仅仅是认识型的理解总结并未深入研究. 一.什么是内存模型,为什么需要它 Java内存模型(Java Memory Model)并发相关的安全发布,同步策略的规范.一致性等都来自于JMM. 1 平台的内存模型 在架构定义的内存模型中将告诉应用程序可以从内存系统中获得怎样的保证,此外还定义了一些特殊的指令(称为内存栅栏或栅栏),当需要共享数据时,这些指令就能实现额外的存储协调保证. JVM通过在适当的位置上插入内存栅栏来屏蔽在JVM与底层平台内存模型之间的

《java并发编程的艺术》读书笔记-第三章Java内存模型(二)

一概述 本文属于<java并发编程的艺术>读书笔记系列,第三章java内存模型第二部分. 二final的内存语义 final在Java中是一个保留的关键字,可以声明成员变量.方法.类以及本地变量.可以参照之前整理的关键字final.这里作者主要介绍final域的内存语义. 对于final域,编译器和处理器要遵守两个重排序规则: 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序. 初次读一个包含final域的对象的引用,与随后初次读这