ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Interception 1.0.1 驱动层键鼠拦截实战:从安装到输入录制回放

Interception 1.0.1 驱动层键鼠拦截实战:从安装到输入录制回放 简介Interception 1.0.1 是一套面向 Windows 平台的开源键盘鼠标输入拦截库适合从事自动化测试、安全监控、游戏辅助及底层驱动开发的程序员参考。它通过设备驱动接口在低层捕获并操纵硬件输入事件帮助开发者理解全局钩子、事件回调注册与线程安全等关键技术。压缩包共 44 个文件约 126KB以 cpp 与 c 源码、h 头文件、makefile 与 cmd 构建脚本为主另含 sources 配置、rc 资源脚本、pdf 文档及 readme 说明覆盖库本体、示例与编译流程。内容预览显示包内提供 interception.c、interception.h 及 samples 目录下的 axes、caps2esc、cadstop 等示例便于对照学习事件处理与多平台差异。目前已有 308 人学习下载读者可借此掌握输入拦截的初始化、回调处理与调试排错思路快速将库集成到自身项目中。1. 从驱动层截获键鼠Interception 1.0.1 到底能解决什么如果你做过按键映射、宏工具、KVM 切换器或者游戏辅助输入层大概率遇到过同一个天花板用SetWindowsHookEx挂全局钩子在普通窗口里跑得好好的一进全屏游戏、一碰带反作弊的进程、或者遇到以管理员权限运行的窗口钩子就哑了。这不是代码写错了而是 Windows 的钩子机制本身有边界——它工作在用户态的消息链上优先级天然低于内核里真正处理 HID 报告的那一层。Interception 1.0.1 就是冲着这个天花板去的它把自己做成一个内核态过滤驱动keyboard.sys/mouse.sys插在键盘鼠标驱动栈中间在硬件层下达基本指令之后、系统把输入分发给各个应用之前把原始的键盘鼠标事件截下来交给你处理。你拿到的是设备级的输入流不是窗口消息所以全屏、高权限、多桌面这些场景都不再是障碍。这份资源的核心是驱动加一套用户态库驱动负责拦截用户态通过interception.dll和interception.h跟驱动通信用interception_send/interception_receive收发事件。它适合三类人一是要做底层按键重映射、组合键、连发宏的开发者二是要写输入录制回放、自动化测试工具的人三是研究输入栈、想做设备级过滤的工程师。它不适合只想改个快捷键的普通用户——装驱动、签名、写代码这套流程对纯使用者来说太重了。下面按「装起来 → 跑通第一个例子 → 处理多设备与上下文 → 避坑 → 进阶」的顺序拆每一步都落到能复现的命令和代码。2. 驱动安装与第一个可运行例子从 inf 到 receive 循环2.1 安装前必须搞清楚的驱动模型Interception 的驱动是过滤驱动filter driver不是功能驱动。它不替代kbdclass/mouclass而是挂在这些类驱动之上所以安装方式跟普通设备驱动不一样。包里通常有install-interception.exe这类安装器也有对应的.inf和.sys。安装的本质是往注册表里写过滤驱动的 UpperFilter 项让系统在加载键盘鼠标类驱动时把 Interception 的过滤驱动一起拉起来。这一步决定了后面所有代码能不能收到事件——驱动没挂上interception_create_context会返回一个无效句柄interception_receive直接返回 0你会以为是代码问题其实是驱动没生效。安装一般需要管理员权限装完建议重启一次让过滤驱动在设备栈重新枚举时挂载。验证是否挂上的办法不是看设备管理器里有没有新设备过滤驱动通常不显示为独立设备而是直接跑一段最小代码看能不能收到事件。我一般会先写个只打印设备 ID 和按键码的循环能打印出来就说明链路通了。2.2 最小可运行代码创建上下文并接收事件下面这段是能直接编译运行的最小例子作用是拦截键盘事件并原样转发同时把扫描码打印出来。它不改变任何输入行为只验证驱动通信是否正常。#include stdio.h #include interception.h int main(void) { // 创建与驱动通信的上下文失败说明驱动没装好或权限不够 InterceptionContext context interception_create_context(); if (context NULL) { printf(create_context failed, driver not loaded?\n); return 1; } // 只过滤键盘鼠标用 INTERCEPTION_FILTER_MOUSE_ALL interception_set_filter(context, interception_is_keyboard, INTERCEPTION_FILTER_KEY_ALL); InterceptionDevice device; InterceptionStroke stroke; while (interception_receive(context, device interception_wait(context), stroke, 1) 0) { if (interception_is_keyboard(device)) { InterceptionKeyStroke *ks (InterceptionKeyStroke *)stroke; printf(device%d code0x%04x state%d\n, device, ks-code, ks-state); } // 原样发回保证系统仍能收到这次输入 interception_send(context, device, stroke, 1); } interception_destroy_context(context); return 0; }逻辑上分四步interception_create_context建立用户态和驱动的通道interception_set_filter告诉驱动你关心哪类事件这里用interception_is_keyboard做设备判定、INTERCEPTION_FILTER_KEY_ALL表示键盘全量事件interception_wait阻塞等下一个事件interception_receive把事件读进stroke最后interception_send把事件转发回去。关键参数是 filter 的粒度——如果你只关心按下可以用INTERCEPTION_FILTER_KEY_DOWN减少无谓唤醒interception_wait返回的是设备句柄多设备场景下靠它区分是哪个键盘。编译时链接interception.lib运行时interception.dll要和 exe 放一起。注意位数必须匹配驱动和用户态库都是分 32/64 位的64 位系统上用了 32 位 dll 会出现上下文创建成功但收不到事件的情况这个坑后面还会细说。2.3 事件结构里几个容易读错的字段InterceptionKeyStroke里最常被误用的是state。它不是简单的 0/1而是包含INTERCEPTION_KEY_DOWN、INTERCEPTION_KEY_UP、INTERCEPTION_KEY_E0、INTERCEPTION_KEY_E1这些标志位。扩展键比如方向键、右 Ctrl会带 E0 标志如果你只判断state 1来识别按下扩展键就会漏掉。正确做法是按位与if (ks-state INTERCEPTION_KEY_DOWN)。code是扫描码不是虚拟键码想转成 VK 得自己查表或者用MapVirtualKey这一点在写映射逻辑时特别关键——很多人拿扫描码当 VK 用结果映射全错位。鼠标事件结构InterceptionMouseStroke里flags区分按键和移动rolling是滚轮增量x/y是相对位移。注意鼠标移动是相对量不是绝对坐标做绝对定位要自己累加维护状态。3. 多设备识别与上下文过滤把「哪个键盘」这件事说清楚3.1 设备 ID 的分配规律与识别方法Interception 给每个设备分配一个从 1 开始的 ID键盘和鼠标共用这个编号空间靠interception_is_keyboard/interception_is_mouse区分类型。多键盘场景下ID 的分配顺序跟设备枚举顺序有关重启或插拔后可能变化所以不能把「ID1 就是我的主键盘」写死。稳妥做法是在收到事件时记录设备特征或者用interception_get_hardware_id拿硬件 ID 做匹配。// 拿到设备硬件 ID用于区分具体是哪个键盘 unsigned short hw_id[512]; int len interception_get_hardware_id(context, device, hw_id, sizeof(hw_id)); if (len 0) { wprintf(Lhardware id: %s\n, hw_id); }interception_get_hardware_id返回的是宽字符硬件 ID 字符串长度写缓冲区字节数。这个 ID 在同一台机器上相对稳定适合做「只拦截某个特定键盘」的白名单逻辑。参数上注意缓冲区给够太小会截断返回长度是实际写入的字节数。3.2 按设备做差异化过滤实际项目里经常需要「键盘 A 正常用键盘 B 走宏逻辑」。做法是在 receive 循环里按设备 ID 分流而不是全局设一个 filter。filter 是全局的但你可以对不需要处理的设备直接interception_send转发对目标设备才做加工。while (interception_receive(context, device interception_wait(context), stroke, 1) 0) { if (interception_is_keyboard(device)) { if (device target_keyboard_id) { // 目标键盘加工后再发或吞掉不发 InterceptionKeyStroke *ks (InterceptionKeyStroke *)stroke; if ((ks-state INTERCEPTION_KEY_DOWN) ks-code 0x1E) { // 把 A 键改成不发实现屏蔽 continue; } } } interception_send(context, device, stroke, 1); }这里continue跳过interception_send就等于吞掉了这次输入系统收不到。这是实现屏蔽、重映射的基础手法。要注意吞掉按键后对应的抬起事件如果还转发系统会收到一个没有按下的抬起某些应用会异常所以成对处理按下和抬起是基本纪律。3.3 上下文与线程模型interception_receive是阻塞的一个上下文对应一个事件队列。如果你在主线程里跑 receive 循环界面就会卡死。常见做法是开独立线程专门跑 receive主线程通过队列或共享状态跟它交互。多个上下文能不能开可以但每个上下文都会独立跟驱动建立通道事件会被分发到哪个上下文取决于驱动的分发策略容易乱。我一般一个进程只开一个上下文用单线程 receive把处理逻辑做轻重活丢给工作线程。提示interception_wait可以配合超时使用但标准接口里它是无限阻塞的。需要可中断的循环时用interception_receive配合设备集合或者自己在收到事件后检查退出标志。4. 避坑与排查驱动装上了却收不到事件的五种情况4.1 现象create_context 成功但 receive 一直返回 0原因通常是位数不匹配。64 位系统上如果编译出 32 位程序却加载了 64 位驱动上下文能创建但事件通道对不上。解决确认 exe、dll、sys 三者位数一致64 位系统统一用 64 位。另一个可能是 filter 设错了设备类型比如对鼠标设了键盘 filter自然收不到。4.2 现象普通窗口能收到全屏游戏里失效这通常不是 Interception 的问题而是游戏用了 Raw Input 或 DirectInput绕过了你处理的那一层。Interception 工作在类驱动过滤层理论上在 Raw Input 之下但某些反作弊会额外挂自己的过滤驱动顺序在你之上把你的拦截架空了。解决确认驱动加载顺序必要时调整 UpperFilter 的注册顺序如果对方是内核级反作弊这条路基本走不通别硬刚。4.3 现象按键吞掉后系统「卡键」原因就是前面说的只吞了按下没吞抬起或者反过来。系统状态机里那个键一直处于按下态表现为一直触发。解决维护一个按键状态表按下和抬起成对处理程序退出前把所有被吞的键补发抬起事件这是血泪经验不加这步调试时能把自己坑死。4.4 现象多键盘时事件串台设备 ID 在插拔后变化写死的 ID 匹配到了别的键盘。解决用硬件 ID 做匹配或者在启动时枚举所有设备建立映射表运行中监听设备变化重新枚举。别依赖固定 ID。4.5 现象安装驱动后系统蓝屏或键鼠失灵过滤驱动写注册表时如果键路径写错或者驱动签名有问题可能导致类驱动加载失败键鼠直接不能用。解决装之前建好系统还原点用官方安装器而不是手动改注册表出问题进安全模式卸载过滤驱动项。这个坑一旦踩上没有后悔药只能靠还原点。5. 进阶用 Interception 做输入录制回放与状态机映射5.1 录制回放的基本结构录制就是把 receive 到的 stroke 按时间戳存下来回放就是按时间间隔用interception_send发出去。难点在时间精度和状态一致性。下面是一个简化的录制结构typedef struct { DWORD tick; // GetTickCount() 时间戳 int is_keyboard; // 1 键盘 0 鼠标 InterceptionStroke stroke; } RecordedEvent; // 录制循环里 RecordedEvent ev; ev.tick GetTickCount(); ev.is_keyboard interception_is_keyboard(device); ev.stroke stroke; fwrite(ev, sizeof(ev), 1, fp); interception_send(context, device, stroke, 1);回放时按tick差值 sleep再interception_send。参数上注意InterceptionStroke是联合体键盘和鼠标共用同一块内存存的时候要按类型正确解释读回来也要按is_keyboard分支处理否则字段会错位。5.2 用状态机做组合键映射组合键的本质是状态机记录当前按下了哪些修饰键当主键按下时判断是否满足组合条件满足就改写事件。比如把 CapsLock 当 Ctrl 用static int caps_down 0; if (ks-code 0x3A) { // CapsLock 扫描码 if (ks-state INTERCEPTION_KEY_DOWN) { caps_down 1; ks-code 0x1D; // 改成左 Ctrl } else { caps_down 0; ks-code 0x1D; } }这里改的是code转发出去的就是 Ctrl 事件。注意修饰键状态要自己维护因为系统看到的是你改写后的事件原始 CapsLock 状态它不知道。复杂组合键建议用一张映射表加状态机别堆 if-else否则扩展键和抬起逻辑会把你绕晕。5.3 验证方法怎么确认拦截真的生效最直接的验证是写一个只打印不转发的程序跑起来后敲键盘如果系统完全没反应但程序在打印说明拦截生效了。再逐步加转发观察系统行为恢复。另一个办法是用mouse test lab这类输入测试工具观察原始事件对比拦截前后的差异。我习惯在改动映射逻辑后先用一个「透传模式」跑一遍确认所有按键都能正常转发再开映射这样能把「拦截问题」和「映射逻辑问题」分开定位。从那以后我每次动输入层代码都强制先跑一遍透传验证再上映射退出前补发所有按下键的抬起事件。这套习惯帮我省了无数次重启。希望帮到你。本文还有配套的精品资源点击获取
返回列表