Windows API Hook 屏幕取词实战:VC 源码解析与避坑指南

发布时间:2026/9/26 18:28:23
Windows API Hook 屏幕取词实战:VC 源码解析与避坑指南 简介这是一份面向Windows开发者的API Hook实战源码聚焦屏幕取词这一典型应用场景适合具备一定C与Win32编程基础、希望深入理解系统级Hook机制的学习者。源码围绕低级鼠标与键盘钩子的安装、事件处理与卸载流程展开演示了如何借助SetWindowsHookEx等接口拦截输入事件并结合GetCursorPos、ScreenToClient、GetWindowText等API完成坐标转换与文本读取同时涉及分词、词性标注等NLP预处理及外部翻译接口调用的思路。压缩包共31个文件约145KB以h头文件与cpp源文件为核心辅以vcproj、sln工程配置、rc资源脚本、dll与lib库文件及exe可执行程序结构完整可直接编译调试。目前已有257人学习下载读者可从中获得Hook安装与管理的完整代码框架、屏幕取词事件处理链路以及Windows系统编程的排错参考。1. 屏幕取词与 API Hook从一份 VC 工程源码说起做桌面翻译工具或辅助阅读软件时屏幕取词几乎是绕不开的功能。用户用鼠标在任意窗口划一下程序就得把选中的文字抓出来再送去翻译或查询。听起来简单真动手才发现难点不在翻译而在“怎么拿到别的进程里的文字”。这份API-HOOK.rar就是一份用 VC 写的 API Hook 源码工程核心目标正是实现划词与屏幕取词。它包含ApiHook.sln、ApiHook.vcproj、ApiHook.dll、ApiHook.exe、ApiHookDlg.cpp、ApiHook_dll.h等文件是一套完整的 Windows Hook 实践骨架。适合谁适合已经会写 Win32 窗口程序、想搞明白 Hook 到底怎么落地、又不想从零搭工程的人。下面我按“它是什么、怎么编译跑起来、Hook 怎么装、坑在哪、怎么验证”一路拆下去。2. 工程结构与 Hook 选型先搞清楚这份源码里有什么2.1 文件清单与模块划分拿到一个.rar源码包我习惯先看目录结构再动手编译不然很容易在缺库、缺头文件上浪费时间。这份工程的文件大致分四类类别文件作用解决方案与工程ApiHook.sln、ApiHook.vcproj、ApiHook.suo、ApiHook.ncbVS 解决方案、工程配置、用户选项与浏览数据库主程序ApiHook.cpp、ApiHookDlg.cpp、ApiHookDlg.h、Resource.h、ApiHook.rc对话框主程序负责界面与 Hook 的安装/卸载入口动态库ApiHook_dll.h、ApiHook.dll、ApiHook.lib把 Hook 逻辑封装进 DLL供注入或加载使用公共头与预编译ApiHook.h、stdafx.h、stdafx.cpp、ReadMe.txt公共声明、预编译头、说明文件.suo、.ncb、.aps这些是 VS 的中间文件换机器或换 VS 版本后基本会重建不用太在意。真正要读的是ApiHookDlg.cpp界面与流程控制、ApiHook.cppHook 主体和ApiHook_dll.hDLL 导出接口。ReadMe.txt通常写了编译顺序和依赖先扫一眼能省不少事。2.2 为什么屏幕取词要选 API Hook屏幕取词的本质是用户在其他进程的窗口里划选文字你的程序要拿到这段文字。直接读对方窗口控件是不行的因为跨进程内存访问受保护GetWindowText对很多自绘控件也拿不到内容。常见做法是 Hook 与文本获取相关的 API在目标进程调用这些函数时截获参数或返回值。Windows 下实现 Hook 有几条路SetWindowsHookEx适合拦截消息级事件鼠标、键盘、窗口过程Detour / EasyHook 这类库适合拦截任意函数调用粒度更细。这份源码走的是SetWindowsHookEx路线配合 DLL 注入属于比较经典、依赖少、容易讲清楚的方案。选它的理由很直接不需要额外第三方库VC 工程自带编译出来就能跑适合拿来理解 Hook 的完整生命周期。2.3 低级鼠标钩子与键盘钩子的取舍屏幕取词通常要监听两类事件鼠标按下/抬起确定选区起止和键盘比如按住某键触发取词。对应WH_MOUSE_LL和WH_KEYBOARD_LL两种低级钩子。WH_MOUSE_LL全局鼠标钩子能拿到屏幕坐标适合判断用户在哪里划词。WH_KEYBOARD_LL全局键盘钩子适合做快捷键触发或辅助选择。低级钩子不需要把 DLL 注入到每个进程系统会把事件回调到安装钩子的线程消息队列里实现相对干净。代价是回调必须快不能在里面做耗时操作否则系统会把钩子摘掉。所以取词逻辑一般是钩子里只记录坐标和状态真正的文本提取放到主程序里异步做。提示低级钩子回调运行在安装它的线程上下文若该线程阻塞整个系统的鼠标键盘响应都会受影响。回调里只做标记别做翻译。3. 编译与运行把 ApiHook.sln 跑起来3.1 环境准备与工程打开这份工程是 VC 工程.vcproj格式对应 Visual Studio 2008 到 2013 这一代。用更高版本 VS 打开会触发工程升级向导一般能升但字符集和 MFC 版本可能要对一下。我一般这样做安装带 MFC 的 Visual Studio安装时勾选“MFC 和 ATL 支持”。解压API-HOOK.rar到纯英文路径路径里不要有中文和空格否则链接 DLL 时容易出玄学问题。双击ApiHook.sln若提示升级点确定让 VS 生成新的工程文件。打开后先看解决方案资源管理器里是不是有两个项目一个 exe、一个 dll。如果只有一个说明 DLL 项目没被正确加载需要手动添加ApiHook_dll相关文件。3.2 编译顺序与配置Hook 的常见结构是DLL 里放钩子过程exe 负责加载 DLL 并安装钩子。所以编译顺序应该是先 DLL 后 exe。在 VS 里可以设置项目依赖# 在解决方案上右键 - 项目依赖项 # 让 ApiHookexe依赖 ApiHook_dlldll # 这样生成解决方案时会先编 DLL配置上注意两点平台选Win32这份老工程大概率没有 x64 配置强行切 x64 会缺库。字符集建议用“多字节字符集”因为老代码里GetWindowText、SetWindowsHookEx的调用多半按 ANSI 写的切 Unicode 会有一堆LPCWSTR类型不匹配。编译成功后输出目录下会有ApiHook.exe和ApiHook.dll。如果 DLL 没生成检查ApiHook_dll.h里的导出宏是否和工程设置里的“导出符号”一致。3.3 运行与基本验证直接运行ApiHook.exe界面上一般会有“安装钩子”“卸载钩子”之类的按钮。点安装后去记事本或浏览器里划一段文字看程序有没有反应。// 典型安装钩子的调用形态示意具体以源码为准 HHOOK hMouse SetWindowsHookEx( WH_MOUSE_LL, // 钩子类型低级鼠标 MouseProc, // 钩子回调函数 hInstance, // DLL 实例句柄 0 // 线程 ID0 表示全局 ); if (hMouse NULL) { // 安装失败用 GetLastError 查原因 DWORD err GetLastError(); }SetWindowsHookEx四个参数分别是钩子类型、回调地址、模块句柄、线程 ID。全局低级钩子线程 ID 传 0。返回 NULL 就是失败常见原因是回调函数不在 DLL 里对全局钩子而言或权限不够。验证时如果划词没反应先确认钩子安装返回值不是 NULL再看回调里有没有正确判断鼠标位置。4. 取词逻辑实现坐标、文本与消息处理4.1 从鼠标坐标到目标窗口钩子回调拿到的是屏幕坐标要取词得先知道鼠标下是哪个窗口、窗口里哪个控件。常见链路是// 在鼠标回调里记录坐标主程序里处理 POINT pt ((MSLLHOOKSTRUCT*)lParam)-pt; // 屏幕坐标 HWND hWnd WindowFromPoint(pt); // 命中的窗口 HWND hRoot GetAncestor(hWnd, GA_ROOT); // 取顶层窗口WindowFromPoint返回的是最内层窗口可能是子控件GetAncestor用来上溯到顶层窗口。拿到窗口句柄后可以尝试GetWindowText读标题或内容但对编辑框、浏览器渲染内容往往无效这也是屏幕取词最头疼的地方。4.2 文本获取的几种手段源码里可能用到的文本获取方式包括GetWindowText对标准控件有效对自绘控件无效。SendMessage(hWnd, WM_GETTEXT, ...)跨进程发送消息取文本比直接读内存安全。GetCursorPosScreenToClient把屏幕坐标转成窗口客户区坐标用于定位选区。对浏览器或 Office 这类复杂控件往往要配合 Accessibility 接口或 UI Automation源码未必覆盖到。// 跨进程取文本的常见写法 int len (int)SendMessage(hWnd, WM_GETTEXTLENGTH, 0, 0); if (len 0) { TCHAR* buf new TCHAR[len 1]; SendMessage(hWnd, WM_GETTEXT, len 1, (LPARAM)buf); // buf 里就是文本用完 delete[] }WM_GETTEXTLENGTH先拿长度再WM_GETTEXT取内容。注意跨进程SendMessage传指针时系统会做一次编组所以不能传复杂结构体只能传基本类型或系统能识别的缓冲区。4.3 划词与选区的判定划词的核心是判断“用户按住鼠标拖动了一段距离”。在鼠标回调里记录按下点和抬起点两点距离超过阈值才算划词避免单击误触发。// 伪代码划词判定 static POINT g_start {0, 0}; static bool g_dragging false; // 鼠标按下 if (wParam WM_LBUTTONDOWN) { g_start pt; g_dragging true; } // 鼠标抬起 if (wParam WM_LBUTTONUP g_dragging) { int dx abs(pt.x - g_start.x); int dy abs(pt.y - g_start.y); if (dx 5 || dy 5) { // 阈值可调 // 触发取词 } g_dragging false; }阈值 5 像素是经验值太小会把轻微抖动当划词太大则短词选不中。实际项目里还会结合双击选词、三击选段等逻辑源码里若没写可以自己补。注意低级钩子回调里不要直接弹窗或做耗时 IO否则系统可能因为超时把钩子移除表现为“用一会儿就失效”。5. 避坑与排查Hook 失效、取不到词、程序崩溃5.1 钩子安装返回 NULL现象点“安装钩子”没反应日志里SetWindowsHookEx返回 NULL。原因全局钩子的回调函数必须位于 DLL 中如果回调写在 exe 里系统无法把回调映射到其他进程安装就会失败。另外权限不足比如目标进程以管理员运行而你的程序不是也会失败。解决确认MouseProc、KeyboardProc这些回调在 DLL 工程里并且 DLL 已正确加载。用GetLastError看错误码ERROR_HOOK_NEEDS_HMOD基本就是回调不在 DLL 里。5.2 划词没反应但钩子装上了现象钩子安装成功鼠标事件也能进回调但划词后程序没拿到文本。原因多半是文本获取环节失败。GetWindowText对浏览器、PDF 阅读器、自绘控件无效或者坐标转换错了WindowFromPoint拿到的窗口不是真正承载文本的控件。解决在回调里打印hWnd和窗口类名GetClassName确认命中的是什么控件。对浏览器可以尝试 UI Automation对标准编辑框检查是否用了WM_GETTEXT而不是直接读内存。5.3 程序运行一段时间后钩子自动失效现象刚启动能用过几分钟划词就没反应了。原因低级钩子回调有超时限制系统默认约 300 毫秒。如果回调里做了翻译、网络请求或大量计算超时后系统会静默移除钩子。解决回调里只记录坐标和事件类型把取词和翻译放到主线程或工作线程异步处理。可以用PostMessage把事件丢回主窗口避免在回调里做重活。5.4 编译时报 MFC 相关链接错误现象nafxcwd.lib找不到或者_afx...符号未解析。原因工程用了 MFC但当前 VS 没装 MFC 组件或者工程配置里的 MFC 使用方式静态/动态和实际库不匹配。解决在 VS 安装器里勾选“MFC 和 ATL 支持”。工程属性里检查“使用 MFC”是“在共享 DLL 中使用 MFC”还是“在静态库中使用 MFC”两者要和你链接的库一致。5.5 卸载钩子后目标程序异常现象卸载钩子后某些程序鼠标键盘行为异常甚至崩溃。原因UnhookWindowsHookEx调用时机不对或者 DLL 在被其他进程使用时被卸载。全局钩子注入到多个进程卸载时要确保所有进程都释放了 DLL。解决先UnhookWindowsHookEx再FreeLibrary。不要在钩子回调还在执行时卸载。调试阶段可以用WH_MOUSE_LL这种不需要注入的钩子减少对目标进程的影响。6. 进阶技巧用日志与最小复现验证 Hook 行为Hook 调试最难受的地方是“看不见”——钩子到底有没有被调用、参数是什么、返回值对不对全靠猜。我的习惯是给钩子加一层轻量日志把关键事件写到文件里再配合最小复现程序验证。// 钩子回调里写日志注意仅调试用正式版要去掉或异步化 void LogEvent(const char* tag, WPARAM wParam, POINT pt) { FILE* f fopen(hook_log.txt, a); if (f) { fprintf(f, %s wParam%u pt(%d,%d)\n, tag, (unsigned)wParam, pt.x, pt.y); fclose(f); } }日志能回答三个问题回调有没有进、事件类型对不对、坐标是不是预期值。如果日志里根本没有记录说明钩子没装上或没生效如果坐标不对检查 DPI 缩放——高 DPI 下屏幕坐标和实际像素可能差一个缩放系数WindowFromPoint会命中错误窗口。验证时我一般开一个记事本做最小复现在记事本里划词看日志里hWnd是不是记事本的编辑框WM_GETTEXT能不能拿到文字。记事本通过后再去浏览器、PDF 阅读器上试逐步定位是哪类控件不支持。还有一个容易忽略的点32 位和 64 位进程的钩子不能互通。32 位 DLL 装的全局钩子对 64 位进程无效反之亦然。如果目标程序是 64 位而你的工程是 Win32划词在 64 位程序上就会失效。解决办法是分别编译 32 位和 64 位两个 DLL按目标进程位数加载。# 检查目标进程位数任务管理器 - 详细信息 - 平台 # 32 位进程显示“32 位”64 位显示“64 位” # 你的 DLL 位数必须和目标一致最后说个血泪经验Hook 代码写完先别急着集成到主程序单独写一个最小 exe dll 跑通“安装钩子 → 划词 → 拿到文本 → 卸载钩子”全流程再往业务里搬。我早期图省事直接在项目里改结果钩子失效和业务 bug 混在一起排查花了两倍时间。从那以后我每次做 Hook 都强制先走一遍最小复现确认钩子生命周期干净了再集成。希望帮到你。本文还有配套的精品资源点击获取