《Windows 核心编程》27章:硬件输入模型和局部输入状态
内容概括
- 本章主要讨论系统的硬件输入模型
- 重点考察:按键和鼠标事件进入系统、发送给适当的窗口
- 设计输入模型的目标:保证一个线程的动作不影响其他线程
16位 Windows 中的案例:
- 若一个任务引起死循环,所有任务都被挂起,不再响应用户
- 若一个任务死循环后,只能通过重启机器解决,则单个任务的执行影响太大
在 Windows 2000 和 Windows 98 中,一个挂起的线程不会妨碍其他线程接受硬件输入
1. 原始输入线程
当系统初始化时会创建一个原始输入线程和一个系统硬件输入队列,两者构成系统硬件输入模型的核心
- 原始输入线程(raw input thread RIT),简称 RIT
- 系统硬件输入队列(System hardware input queue SHIQ),简称 SHIQ
RIT 处理 Input 消息
Windows 输入消息大多数只有鼠标和键盘两种:
- 对于键盘消息,RIT 会把消息放入跟 RIT 连接的线程的虚拟输入队列
- 对于鼠标消息,RIT 可以确定哪个窗口在鼠标光标之下,通过这个窗口调用 GetWindowThreadProcessId 来确定哪个线程建立了这个窗口,返回该线程的 ID,指出这个 ID 的线程得到鼠标消息
与按键硬件事件的处理不同,在任何时刻下,只有一个线程与 RIT 连接,这个线程称为 “前景线程”(因为它建立与用户交互的窗口,且线程中的窗口相对与其他窗口来说,处于画面的前景)
【例】
在任何时刻下,只有一个线程与 RIT 连接 :
按键消息进入虚拟输入队列:
不同的线程如何连接到 RIT
- 连接前景线程:当创建一个进程时,进程的线程可以建立一个窗口,使创建窗口的线程同 RIT 连接(鼠标放在上面点一下)
- 用键盘激活的窗口:RIT 要负责处理特殊的键组合:Alt + Tab、Alt + Esc、Ctrl + Alt + Del 等,处理这些键组合时,可以保证总能用键盘激活窗口。 因为应用程序不能废弃这些键组合功能,当用户按下某个特殊组合时, RIT 激活选定的窗口,并将窗口的(创建)线程连接到 RIT
- Windows 也提供激活窗口的功能,使窗口的线程连接到 RIT
注意:
- 若当前的激活窗口无反应,可以通过按下 Alt + Tab 切换到其他窗口,不会有任何问题,除非电脑由于某种原因死机
- 切换到其他窗口后,即使线程、窗口都没有响应,用户也可以对窗口一直输入(输入,但无响应的状态!就是那样!)
2. 局部输入状态
每个线程都有自己的输入状态变量,每个线程都有不同的焦点窗口、鼠标捕获窗口等概念
2.1 键盘输入与焦点
- RIT 使用户的键盘输入流向一个线程的虚拟输入队列,而不是流向一个窗口(不涉及具体窗口)
- 当线程调用 GetMessage 时,键盘事件从队列中移出,并分派给当前有输入焦点的窗口(调用接口的线程建立的窗口 )
2.2 激活窗口函数
2.2.1 SetActiveWindow 和 GetActiveWindow
- 激活系统中一个最高层(top-level)的窗口,并对这个窗口设定焦点
- 同 SetFocus 函数一样,若调用线程没有创建(作为函数参数的)窗口,则这个函数什么也不做
HWND SetActiveWindow(HWND hwnd);
- 与 SetActiveWindow 配合的函数 : GetActiveWindow
- 与 GetFocus 函数差不多,不同在于,此函数返回(由调用线程的局部输入状态变量 所指出的) 活动窗口的句柄
HWND GetActiveWindow();
2.2.2 BringWindowToTop 和 SetWindowPos
- 两个都是可以改变窗口的 Z 序(Z-order)、活动状态、焦点状态的函数
- BringWindowToTop 函数在内部调用 SetWindowPos 实现,以 HWND_TOP 作为第二个参数
- 若调用这两个函数的线程没有连接到 RIT,则函数什么也不做(无法操控状态变量值)
- 若调用这两个函数的线程与 RIT 连接,即使指定的窗口不是调用线程的,系统也会激活该窗口,且创建窗口的线程被连接到 RIT (会引起调用线程、新连接到 RIT 的线程的局部输入状态变量的更新)
BOOL BringWindowToTop(HWWD hwnd);BOOL SetWindowPos(HWND hwnd,HWND hwndInsertAfter,int x,int y,int cx,int cy,UINI fuFlags);
2.2.3 SetForegroundWindow
- 仅当调用一个函数的线程,已经连接到 RIT 或者当前与 RIT 连接的线程在一定时间内(时间由 SystemParametersInfo 函数和 SPI_SETFOREGROUND_LOCKTIMEOUT 值来控制)没有接收到任何输入时,此函数才有效,若有一个菜单(GUI 的基础部分)是活动的,此函数失效
- 若不允许 SetForegroundWindow 将窗口移到前景,它会闪烁窗口标题栏和任务条的窗口按钮,提示用户应该手工激活,有报告信息输出,可以用 SystemParametersInfo 函数和 SPI_SETFOREGROUNDFLASHCOUNT 值来控制闪烁
为了使这个函数内容更完整,系统提供了另外的一些函数:
AllowSetForegroundWindow
- 调用 AllowSetForegroundWindow 函数可使指定进程的一个线程成功调用 SetForegroundWindow
- 为了使任何进程都可以在线程的窗口上,弹出一个窗口,指定 ASFW_ANY(定义为-1)作为 dwProcessId 参数
BOOL AllowSetForegroundWindow(DWORD dwProcessId);
LockSetForegroundWindow
- 锁定 SetForegroundWindow 函数,使它总是失效的
- 对于 uLockCode 参数可以指定 LSFW_LOCK 或 LSFW_UNLOCK
BOOL LockSetForegroundWindow(UINI uLockCode);
- 当一个菜单被激活时,系统在内部调用这个函数,这样试图跳到前景的窗口,不能关闭这个的菜单
【例】
- Windows 在显示 Start 菜单时,需要明确地调用这些函数,因为 Start 菜单不是一个内置菜单
- 当用户按下 Alt 或将一个窗口拉到前景时,系统自动解锁 SetForegroundWindow 函数,这样可以防止一个程序一直对 SetForegroundWindow 函数封锁
2.2.4 同步键状态
- 每个线程的局部输入状态变量,都包含一个同步键状态数组
- 所有的线程共享一个同步键状态数组,这些数组反应了:在任何给定时刻键盘所有键的状态
用 GetAsyncKeyState 函数,确定用户当前是否按下了键盘的一个键:
SHORT GetAsyncKeyState(int nVirtKey);
- nVirtKey 指出要检查键的虚键代码
- 返回的结果高位指出该键当前是否被按下:是为1,否为0
- 可以用来检查用户是否释放了某些按键
2.3 鼠标光标管理
2.3.1 ClipCursor
- ClipCursor 函数将鼠标光标剪贴到一个矩形区域
- 使鼠标被限制在一个由 prc 参数指定的矩形区域内
BOOL ClipCursor(CONST RECT *prc);
允许剪贴鼠标光标可能会对其他线程产生不利的影响,而不允许剪贴鼠标光标会影响调用线程,系统实现了一种折中的方案:
- 当一个线程调用这个函数时,程序将光标剪贴到指定的矩形区域
- 但若同步激活事件(用户点击其他程序的窗口、调用了 SetForgroundWindow,或按下了 Ctrl + Esc 组合键)发生,系统会停止剪贴鼠标光标的移动,允许鼠标在整个屏幕上自由移动
2.3.2 鼠标捕获
捕获调用 SetCapture,释放调用 ReleaseCapture
- 当一个窗口捕获鼠标时,它要求所有的鼠标消息从 RIT 发到调用线程的虚拟输入队列,并且所有的鼠标消息从虚拟输入队
列发到设置捕获的窗口,在调用 ReleaseCapture 之前,要一直持续这种鼠标消息的捕捉
鼠标的捕获必须与系统的强壮性折衷:
- 当一个程序调用 SetCapture 时,RIT 将所有鼠标信息放入线程的虚拟输入队列
- SetCapture 要为调用 SetCapture 的线程设置局部输入状态变量
做和不做鼠标捕获时,消息传递情况:
- 一个程序在用户按一个鼠标按钮时调用 SetCapture
- 鼠标没有按下时,RIT 不再将鼠标信息发往线程的虚拟输入队列,而是发往与鼠标光标所在的窗口相联系的输入队列
- 也可以说,当用户释放了所有鼠标按钮时,鼠标捕获不再 在全系统范围内执行,而是在一个线程的局部范围内执行
若用户想激活一个其他线程所建立的窗口,系统自动向设置捕获的线程发送:鼠标按钮按下、鼠标按钮放开的消息,然后更新线程的局部输入状态变量,指出该线程不再有鼠标捕获
3. 将虚拟输入队列同局部输入状态挂接在一起
- 由上述讨论得:输入模型是强壮的,每个线程都有自己的局部输入状态环境,在必要时,每个线程还可以连接到 RIT 或从 RIT 断开
3.1 AttachThreadInput 函数
- 可以通过多次调试 AttachThreadInput 函数让多个线程共享一个虚拟输入队列和局部输入状态变量
- 可以利用 AttachThreadInput 函数来强制两个或多个线程共享同一个虚拟输入队列和一组局部输入状态变量
BOOL AttachThreadInput(DWORD idAttach, //线程的ID,该线程所包含的虚拟输入队列是你不想再使用的DWORD idAttachTo, //另一个线程的ID,线程包含的虚拟输入队列,是想让两个线程共享的BOOL fAttach; //共享设为TRUE, 分开设为 FALSE
)
3.2 案例
线程 A 调用 AttachThreadInput,传递线程 A 的 ID 作为第一个参数,线程 B 的 ID 作为第二个参数, TRUE 作为最后一个参数:
AttachThreadInput(AThreadID, BThreadID, TRUE);
- 发往 A1、B1、B2 的硬件输入事件,都将添加到线程 B 的虚拟输入队列中
- 线程 A 的虚拟输入队列不再接收输入事件,除非再调用一次 AttachThreadInput 最后一个参数设为 FALSE
3.3 总结
两个线程的输入都挂接在一起时,使线程共享单一的虚拟输入队列、同一组局部输入状态变量,但线程仍然使用自己的 登记消息队列、发送消息队列、应答消息队列和唤醒标志。
使用 AttachThreadInput 函数存在的问题:
- 若让所有的线程都共享一个输入队列,就会严重削弱系统的强壮性
- 若某一个线程接收一个按键消息并且挂起,其他的线程就不能接收任何输入
综上所述,应该尽量避免使用 AttachThreadInput 函数,但也不是绝对的,以下列举两种使用 AttachThreadInput 的情况
安装日志记录挂钩(journal record hook)或日志播放挂钩(journal playback hook)时
- 当挂钩被卸载时,系统自动恢复所有线程,这样线程就可以使用 (挂钩安装前它们所使用的相同)输入队列
- 当某个线程安装日志记录挂钩时,这个线程通常将 “硬件事件的通知信息” 保存或记录在一个文件上,因输入必须按进入的次序来记录,所以系统中每个线程要共享一个虚拟输入队列,使所有的输入处理同步
设某程序建立了两个线程:
- 第一个线程建立了一个对话框,在这个对话框建立之后,第二个线程调用 GreatWindow,使用 WS_CHILD 风格,并向这个子窗口的双亲传递对话框的句柄
- 系统用子窗口的线程调用 AttachThreadInput,让子窗口的线程使用(对话框线程所使用的)输入队列,这样就使对话框的 所有子窗口之间,对输入强制同步
3.4 两个示例程序(略)
《Windows 核心编程》27章:硬件输入模型和局部输入状态相关推荐
- Windows核心编程 第27章 硬件输入模型和局部输入状态
第27章 硬件输入模型和局部输入状态 这章说的是按键和鼠标事件是如何进入系统并发送给适当的窗口过程的.微软设计输入模型的一个主要目标就是为了保证一个线程的动作不要对其他线程的动作产生不好的影响. 27 ...
- Windows核心编程笔记(二十一) 硬件输入模型和局部输入状态
28.1 原始输入线程(RIT) (1)图解硬件输入模型 ①当操作系统初始化时会创建一个原始输入线程(RIT)和系统硬件消息队列(SHIQ),这两者是系统硬件输入模型的核心.当SHIQ队列有硬件(如鼠 ...
- Windows核心编程 第九章 线程与内核对象的同步(下)
9.4 等待定时器内核对象 等待定时器是在某个时间或按规定的间隔时间发出自己的信号通知的内核对象.它们通常用来在某个时间执行某个操作. 若要创建等待定时器,只需要调用C r e a t e Wa i ...
- windows核心编程-第二章 Unicode
第2章U n i c o d e 随着M i c r o s o f t公司的Wi n d o w s操作系统在全世界日益广泛的流行,对于软件开发人员来说,将目标瞄准国际上的各个不同市场,已经成为一个 ...
- windows核心编程-第一章 对程序错误的处理
第一章-对程序错误的处理 在开始介绍Microsoft Windows 的特性之前,必须首先了解 Wi n d o w s的各个函数是如何进行错误处理的. 当调用一个Wi n d o w s函数时,它 ...
- Windows核心编程 第九章 线程与内核对象的同步(上)
第9章 线程与内核对象的同步 上一章介绍了如何使用允许线程保留在用户方式中的机制来实现线程同步的方法.用户方式同步的优点是它的同步速度非常快.如果强调线程的运行速度,那么首先应该确定用户方式的线程同步 ...
- [C++]《Windows核心编程》读书笔记
这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入 ...
- C++Windows核心编程读书笔记(转)
http://www.makaidong.com/(马开东博客) 这篇笔记是我在读<windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的 ...
- Windows核心编程之核心总结(第一章 错误处理)(2018.5.26)
前沿 学习Windows核心编程是步入Windows编程殿堂的必经之路,2018年寒假重温了计算机操作系统知识,前阵子又过学习Windows程序设计方面的基础,正所谓打铁要乘热,所以我又入了Windo ...
最新文章
- MySQL之模糊查询
- java jnlp被阻止_JNLP应使用特定的Java版本,但会出现错误结果
- matlab-等高线图-三维曲线的绘制
- 关于java中敏感词检测的一些总结
- 感知重塑与忠诚建立:车企营销的两大新机遇
- 字节流写数据搭配异常处理
- ecshop ipdel.php,去除Ecshop后台调用api.ecshop.com官网后门代码
- Qt4_Laying Out Widgets Widgets
- 港科大郑光廷院士问诊未来,揭露 AI 最新应用与实践
- python不正确的关系字符_Python系列之 - 字符编码问题
- Flash Player9.0 跟Flash Player8.0区别
- 图像局部特征(十二)--BRISK特征
- 忘了 忘了,以前学的矩阵知识全交给老师了,敲黑板了,矩阵乘法实例讲解
- 微软运行库合集 |VC9、VC11、VC14、VC15库 32位 64位|v2019.3.2(3264位)最新版 phpstudy vc9-vc14运行库
- 有一个测试微信删除软件叫wool,微信自动检测僵尸粉软件有哪些(这款软件我一直在用)...
- codeforces [Gym-100814E]
- elementui日历组件实现可标记日历
- Google开源C++单元测试框架gTest 5:死亡测试
- UVA 11584 Partitioning by Palindromes
- javascript里将函数名字符串转为函数并执行
热门文章
- 数字与千分位字符串互转
- Spring Cloud Gateway 限流适配多规则的解决方案
- Flink基础高频知识点全面总结
- 安全终端模拟软件推荐
- 这34道接口测试 Jmeter面试题,你会吗?
- 什么软件可以提取视频中的音频?看完这篇文章你就知道了
- vue初始化项目出现unable to access ‘https://github.com/nhn/raphael.git/’解决有效 2021-12-30
- “2023热心肠奖学金”开启申报
- 计算机网络安全技术论文范文,计算机方面论文范文数据库,与关于计算机网络安全防护技术的相关毕业论文题目范文...
- Java静态变量使用实例