MFC实现全局键盘鼠标钩子:跨进程输入捕获技术详解
简介本资源是一个基于MFC框架实现Windows全局键盘与鼠标监控的完整开发项目面向C中级开发者及Windows系统编程学习者解决跨进程捕获用户输入事件的技术难点适用于日志审计、行为分析或辅助工具开发等场景。压缩包共80个文件含6个核心头文件.h与3个源文件.cpp构成主程序与钩子DLL逻辑2个动态库.dll和1个可执行文件.exe支持即装即用另有编译中间文件.obj、.pdb、.tlog等和VS工程配置.sln、.vcxproj、.filters总大小66.54MB。目前已有1398人学习下载。读者可直接运行Release目录下的HookApp.exe实时查看按键码、鼠标坐标等信息并通过HOOK.dll源码深入理解WH_KEYBOARD_LL/WH_MOUSE_LL低级钩子注册、跨模块消息传递及文件记录如CFile写入等关键实现项目结构清晰Debug/Release双配置完备便于调试对比与二次开发。1. MFC 全局钩子获取键盘输入和鼠标输入不是“监听自己窗口”而是穿透所有进程抓取原始输入事件你写了个MFC对话框程序想统计用户在Word里敲了多少个回车、在Chrome地址栏点了几次右键——结果发现WM_KEYDOWN和WM_MOUSEMOVE消息根本收不到。因为这些消息只发给当前活动窗口所属进程而你的MFC程序既不是Word也不是Chrome的进程。这时候常规消息循环彻底失效。本项目就是为解决这个“跨进程输入捕获”刚需而生它用 Windows 的SetWindowsHookExW注册WH_KEYBOARD_LL和WH_MOUSE_LL两类低级全局钩子在系统级拦截所有键盘按键与鼠标动作无论焦点落在哪个应用上。核心价值在于——它不依赖目标进程配合不修改注册表不注入DLL纯静态链接所有逻辑封装在单个 MFC 对话框工程内编译即用。适合做用户行为分析工具、无障碍辅助软件、远程控制端输入采集模块或教学演示“系统级事件拦截”的底层机制。注意它不是键盘记录器keylogger的变种不保存敏感内容所有输入数据仅在内存中短时流转、可由你完全控制处理逻辑。2. 工程结构与技术选型MFC Win32 钩子 API 的轻量级组合拒绝过度封装这个项目没有用 Qt、Electron 或 .NET也没有引入 Boost、Poco 等第三方库。它回归 Win32 原生能力用最精简的 MFC 框架承载系统级钩子逻辑。选择 MFC 而非纯 Win32 SDK是因为它提供了现成的 UI 线程模型、资源管理对话框、控件和消息泵省去手动创建窗口类、处理GetMessage/TranslateMessage/DispatchMessage的繁琐而钩子回调函数必须是C 风格导出函数不能是类成员函数所以整个架构是“MFC 主线程 独立 C 钩子函数 进程间共享数据区”的混合体。这种设计牺牲了一点面向对象的优雅但换来极高的稳定性和调试可见性——你能在 Visual Studio 里直接断点进钩子函数看到wParam是WM_KEYDOWN还是WM_SYSKEYDOWNlParam解析出的虚拟键码是否为VK_CONTROL。下面拆解真实目录结构与关键文件职责。2.1 项目文件清单与角色分工项目解压后共 12 个文件核心是以下 5 个文件名类型作用说明KeyMouseHook.h头文件定义钩子共享数据结构HOOK_DATA含键盘状态数组、鼠标坐标、时间戳、钩子安装/卸载函数声明、以及CALLBACK函数原型KeyMouseHook.cppC 源文件实现InstallHook()/UninstallHook()调用SetWindowsHookExW并保存HHOOK句柄包含g_hHookKeyboard和g_hHookMouse全局变量LowLevelKeyboardProc.cppC 源文件.cpp但用extern C导出唯一真正的钩子回调函数处理WH_KEYBOARD_LL。必须用__declspec(dllexport)标记且函数签名严格匹配LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)LowLevelMouseProc.cppC 源文件同上另一个钩子回调函数处理WH_MOUSE_LL。同样需extern C和导出声明解析MSLLHOOKSTRUCT*结构体获取鼠标动作类型、位置、按键状态CMFCKeyMouseDlg.cppMFC 对话框类实现主界面逻辑点击“开始监听”调用InstallHook()点击“停止监听”调用UninstallHook()通过PostThreadMessage向钩子线程发送控制指令如清空日志定时器刷新 UI 显示最新输入事件提示LowLevelKeyboardProc.cpp和LowLevelMouseProc.cpp必须设置为“不使用预编译头”Properties → C/C → Precompiled Headers → Not Using Precompiled Headers否则#include windows.h可能被跳过导致KBDLLHOOKSTRUCT类型未定义。2.2 钩子安装流程从 MFC 点击事件到系统级拦截的完整链路钩子不是“一装就灵”它需要精确的线程上下文和权限配合。本项目采用UI 线程安装 全局钩子模式这是 MFC 场景下最稳妥的做法。流程如下用户点击对话框上的 “开始监听” 按钮触发CMFCKeyMouseDlg::OnBnClickedBtnStart()该函数调用InstallHook()内部执行// KeyMouseHook.cpp HHOOK g_hHookKeyboard NULL; HHOOK g_hHookMouse NULL; BOOL InstallHook() { // 获取当前线程ID即MFC UI线程ID DWORD dwThreadId GetCurrentThreadId(); // 安装键盘低级钩子第三个参数hMod必须为本模块句柄 g_hHookKeyboard SetWindowsHookExW( WH_KEYBOARD_LL, // 钩子类型低级键盘 (HOOKPROC)LowLevelKeyboardProc, // 回调函数地址必须是C函数 AfxGetInstanceHandle(), // 当前模块实例句柄MFC自动提供 0 // 0表示全局钩子所有线程 ); if (!g_hHookKeyboard) { DWORD err GetLastError(); // 错误码5拒绝访问常见于UAC高权限进程未以管理员运行 return FALSE; } // 同理安装鼠标钩子 g_hHookMouse SetWindowsHookExW( WH_MOUSE_LL, (HOOKPROC)LowLevelMouseProc, AfxGetInstanceHandle(), 0 ); return (g_hHookKeyboard g_hHookMouse); }SetWindowsHookExW成功后Windows 系统会将后续所有键盘/鼠标事件在分发给目标窗口前先调用我们注册的LowLevelKeyboardProc和LowLevelMouseProc钩子函数处理完事件后必须调用CallNextHookEx将事件继续传递否则其他程序会失灵比如按 AltTab 切换窗口失效。注意SetWindowsHookExW的第四个参数dwThreadId设为0表示全局钩子但它要求调用线程必须有消息循环GetMessage。MFC 对话框的DoModal()或主框架的Run()已内置消息泵因此无需额外创建线程——这是本项目能“单线程跑通”的关键前提。2.3 数据共享机制用PostThreadMessage在钩子回调与 UI 线程间安全通信钩子回调函数LowLevelKeyboardProc运行在系统钩子线程由 Windows 创建非你的主线程而 UI 更新如在 ListCtrl 中添加一行日志必须在 MFC 主线程执行。直接跨线程操作 UI 控件如m_listLog.InsertItem()会导致崩溃。本项目采用 Windows 最轻量的线程间通信方式PostThreadMessage。在CMFCKeyMouseDlg::OnInitDialog()中保存主线程 IDm_dwMainThreadId GetCurrentThreadId(); // 成员变量 DWORD m_dwMainThreadId;在LowLevelKeyboardProc中当捕获到有效按键时// LowLevelKeyboardProc.cpp extern C __declspec(dllexport) LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode 0) { // 必须检查nCode否则可能被系统忽略 KBDLLHOOKSTRUCT* pkbhs (KBDLLHOOKSTRUCT*)lParam; if (wParam WM_KEYDOWN || wParam WM_SYSKEYDOWN) { // 构造自定义消息参数 WPARAM wparam MAKELONG(pkbhs-vkCode, pkbhs-scanCode); LPARAM lparam MAKELONG(GetTickCount(), pkbhs-flags); // 发送给MFC主线程非阻塞安全 PostThreadMessage(g_dwMainThreadId, WM_USER_LOG_KEY, wparam, lparam); } } // 关键必须调用CallNextHookEx否则事件中断 return CallNextHookEx(g_hHookKeyboard, nCode, wParam, lParam); }在CMFCKeyMouseDlg中重载WindowProc或使用ON_MESSAGE宏处理WM_USER_LOG_KEY// CMFCKeyMouseDlg.h afx_msg LRESULT OnLogKey(WPARAM wParam, LPARAM lParam); // CMFCKeyMouseDlg.cpp BEGIN_MESSAGE_MAP(CMFCKeyMouseDlg, CDialogEx) ON_MESSAGE(WM_USER_LOG_KEY, CMFCKeyMouseDlg::OnLogKey) END_MESSAGE_MAP() LRESULT CMFCKeyMouseDlg::OnLogKey(WPARAM wParam, LPARAM lParam) { WORD vkCode LOWORD(wParam); WORD scanCode HIWORD(wParam); DWORD tickCount LOWORD(lParam); CString strLog; strLog.Format(_T(KEYDOWN: VK%d, Scan%d, Time%u), vkCode, scanCode, tickCount); m_listLog.InsertItem(0, strLog); // 安全在UI线程执行 m_listLog.SetItemText(0, 1, _T(Keyboard)); return 0; }这种设计避免了全局变量竞争、临界区锁、以及SendMessage可能引发的线程死锁是 Win32 多线程 UI 开发的血泪经验。3. 核心钩子函数详解逐行解析LowLevelKeyboardProc与LowLevelMouseProc钩子函数是整个项目的心脏代码虽短各约 30 行但每行都关乎稳定性。下面以LowLevelKeyboardProc.cpp为例逐段讲解其逻辑、参数含义及易错点。LowLevelMouseProc.cpp结构类似差异点会单独标注。3.1LowLevelKeyboardProc如何从原始扫描码还原用户意图// LowLevelKeyboardProc.cpp #include windows.h #include KeyMouseHook.h // 全局变量必须在钩子函数外定义供InstallHook使用 HHOOK g_hHookKeyboard NULL; DWORD g_dwMainThreadId 0; // 用于PostThreadMessage // 导出函数必须用extern C防止C名字修饰且__declspec(dllexport) extern C __declspec(dllexport) LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { // 【关键检查1】nCode 0 表示系统正在通知钩子移除必须直接返回 if (nCode 0) { return CallNextHookEx(g_hHookKeyboard, nCode, wParam, lParam); } // 【关键检查2】wParam 必须是 WM_KEYDOWN/WM_SYSKEYDOWN/WM_KEYUP/WM_SYSKEYUP // 这里只处理按下事件避免重复日志 if (wParam ! WM_KEYDOWN wParam ! WM_SYSKEYDOWN) { return CallNextHookEx(g_hHookKeyboard, nCode, wParam, lParam); } // 【核心解析】lParam 指向 KBDLLHOOKSTRUCT 结构体 KBDLLHOOKSTRUCT* pkbhs (KBDLLHOOKSTRUCT*)lParam; // 【重要标志位】pkbhs-flags 的 LLKHF_INJECTED 表示事件由 SendInput 生成非物理按键 // 若需过滤模拟输入可加判断if (pkbhs-flags LLKHF_INJECTED) return ...; // 【虚拟键码】vkCode 是 Windows 定义的标准键码如 VK_A65, VK_ESCAPE27 // 注意它不随键盘布局变化不像 scanCode是识别按键的首选 BYTE vkCode pkbhs-vkCode; // 【扫描码】scanCode 是硬件层编码同一物理键在不同键盘上可能不同 // 例如笔记本 Fn 组合键常通过 scanCode 区分 WORD scanCode pkbhs-scanCode; // 【扩展键】如果是右 Ctrl/Altflags 会包含 LLKHF_EXTENDED BOOL bIsExtended (pkbhs-flags LLKHF_EXTENDED) ? TRUE : FALSE; // 【时间戳】GetTickCount() 在钩子线程中调用精度约15ms够用 DWORD dwTime GetTickCount(); // 【安全通信】向UI线程发送日志消息 PostThreadMessage(g_dwMainThreadId, WM_USER_LOG_KEY, MAKEWPARAM(vkCode, scanCode), MAKELPARAM(dwTime, bIsExtended)); // 【强制传递】必须调用CallNextHookEx否则系统事件流中断 return CallNextHookEx(g_hHookKeyboard, nCode, wParam, lParam); }参数说明与踩坑点nCode钩子通知码。HC_ACTION0表示正常事件-1表示钩子即将被移除其他负值多为系统内部用途。不检查nCode 0直接处理会导致钩子卸载时崩溃。wParam消息类型。WM_KEYDOWN256是普通键按下WM_SYSKEYDOWN260是 Alt组合键如 AltTab。漏掉WM_SYSKEYDOWN会丢失系统热键事件。lParam指向KBDLLHOOKSTRUCT的指针。结构体定义在winuser.h中关键字段vkCode虚拟键码Virtual-Key Code范围 0–255VK_SHIFT16,VK_F1112。scanCode扫描码Scan Code硬件相关0x2A是左 Shift0x36是右 Shift。flags标志位LLKHF_INJECTED0x10模拟输入LLKHF_ALTDOWN0x20Alt 键被按住。CallNextHookEx绝对不可省略。它的作用是将事件传递给下一个钩子如果有或最终目标窗口。省略后所有键盘功能瘫痪打字无效、快捷键失灵。3.2LowLevelMouseProc如何区分单击、双击、滚轮与拖拽鼠标钩子比键盘更复杂因为一个“点击”动作涉及WM_MOUSEMOVE、WM_LBUTTONDOWN、WM_LBUTTONUP多个消息。本项目只捕获原始事件不做手势识别如双击但提供足够字段供上层计算。// LowLevelMouseProc.cpp #include windows.h #include KeyMouseHook.h HHOOK g_hHookMouse NULL; DWORD g_dwMainThreadId 0; extern C __declspec(dllexport) LRESULT CALLBACK LowLevelMouseProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode 0) { return CallNextHookEx(g_hHookMouse, nCode, wParam, lParam); } // 【鼠标消息类型】wParam 是鼠标消息ID // WM_MOUSEMOVE512, WM_LBUTTONDOWN513, WM_LBUTTONUP514, WM_MOUSEWHEEL522 UINT msg (UINT)wParam; // 【鼠标结构体】lParam 指向 MSLLHOOKSTRUCT MSLLHOOKSTRUCT* pms (MSLLHOOKSTRUCT*)lParam; // 【坐标】pt.x/pt.y 是屏幕坐标非客户区单位像素 POINT pt pms-pt; // 【鼠标键状态】wParam 可直接判断但更可靠的是看 flags // flags: LLMHF_LOWERED0x01按钮按下, LLMHF_RAISED0x02按钮释放 DWORD flags pms-flags; // 【滚轮】如果 msg WM_MOUSEWHEEL则 GET_WHEEL_DELTA_WPARAM(pms-mouseData) 得到滚轮增量 // 正数向上滚负数向下滚通常 ±120 为一格 SHORT wheelDelta 0; if (msg WM_MOUSEWHEEL) { wheelDelta GET_WHEEL_DELTA_WPARAM(pms-mouseData); } // 【时间戳】同键盘钩子 DWORD dwTime GetTickCount(); // 【发送日志】构造参数并投递 WPARAM wparam MAKEWPARAM(msg, wheelDelta); LPARAM lparam MAKELPARAM(pt.x, pt.y); PostThreadMessage(g_dwMainThreadId, WM_USER_LOG_MOUSE, wparam, lparam); return CallNextHookEx(g_hHookMouse, nCode, wParam, lParam); }关键字段解析MSLLHOOKSTRUCT::pt屏幕坐标Screen Coordinates原点在左上角pt.x0, pt.y0是屏幕左上角。这与WM_MOUSEMOVE的客户区坐标完全不同是全局定位的基础。MSLLHOOKSTRUCT::mouseData对WM_MOUSEWHEEL存储滚轮 delta对其他消息存储额外信息如LLMHF_FROMTOUCH0x08表示触控板输入。MSLLHOOKSTRUCT::flags比wParam更可靠地反映按钮状态。例如LLMHF_LOWERED表示按钮正被按下即使wParam是WM_MOUSEMOVE只要 flags 有此标志说明鼠标在拖拽中。提示若需实现“双击检测”不能只靠钩子函数。应在CMFCKeyMouseDlg::OnLogMouse()中缓存最近两次WM_LBUTTONDOWN的时间与坐标当间隔 500ms 且距离 4 像素时判定为双击——这是 UI 线程可控的逻辑。4. 编译、运行与权限配置绕过 UAC 和 64 位兼容性陷阱项目源码在 VS2019/VS2022 下可直接打开编译但实际运行时极易因权限或平台问题失败。以下是经过实测的完整配置清单覆盖 95% 的首次运行翻车场景。4.1 必须以管理员身份运行的三大原因UAC 限制从 Windows Vista 起全局钩子WH_*_LL除外默认被 UAC 隔离。虽然WH_KEYBOARD_LL和WH_MOUSE_LL是“低级”钩子理论上不需要管理员权限但当目标进程是高完整性级别如以管理员运行的 CMD、PowerShell、某些杀毒软件时你的普通权限钩子无法拦截其输入。现象在记事本里能捕获按键但在管理员模式的 CMD 里按任何键都无日志。Session 0 隔离Windows 服务运行在 Session 0而用户桌面在 Session 1。若你的程序被误判为服务组件钩子可能注册到错误 Session。以管理员运行可确保进入正确 Session。驱动级保护部分安全软件如 Windows Defender Application Guard会阻止非签名进程安装钩子。管理员权限是绕过此类启发式检测的最低门槛。解决方案右键点击生成的.exe→ “以管理员身份运行”或在项目属性中强制提升权限Project Properties → Configuration Properties → Linker → Manifest File → Enable User Account Control (UAC) → Yes再设置UAC Execution Level → requireAdministrator。4.2 32 位 vs 64 位为什么你的钩子在 Win10/11 上“有时有效有时无效”这是一个经典陷阱SetWindowsHookExW要求钩子 DLL或 EXE的位数必须与目标进程一致。但WH_KEYBOARD_LL是例外——它由系统在内核态处理然后回调到你的进程因此32 位程序可以钩住 64 位进程的键盘事件反之亦然。然而鼠标钩子WH_MOUSE_LL在跨位数时存在兼容性问题尤其在 Win10 1903 版本后。实测结论如果你只关心键盘输入如密码强度分析工具编译为x8632位即可兼容性最好如果必须同时捕获鼠标且目标环境是 Win10/11 64位必须编译为 x64。否则在 Edge、Chrome 等 64 位浏览器中鼠标移动可能丢失。配置步骤VS 中切换平台Build → Configuration Manager → Active solution platform → New... → x64检查KeyMouseHook.cpp中AfxGetInstanceHandle()返回的HMODULE是否为 64 位句柄调试时看值是否 0xFFFFFFFF编译后用Dependency Walker或dumpbin /headers yourapp.exe确认machine字段为x64。4.3 常见问题排查5 条血泪经验总结现象 1点击“开始监听”无反应日志列表始终为空原因SetWindowsHookExW返回NULL但代码未检查GetLastError()。常见错误码ERROR_ACCESS_DENIED (5)未以管理员运行ERROR_INVALID_HANDLE (6)AfxGetInstanceHandle()返回NULLMFC 初始化失败ERROR_NOT_ENOUGH_MEMORY (8)系统资源耗尽罕见。解决在InstallHook()中添加错误日志if (!g_hHookKeyboard) { DWORD err GetLastError(); AfxMessageBox(_T(键盘钩子安装失败错误码) CString(err)); return FALSE; }现象 2钩子安装成功但只能捕获自己程序的按键无法捕获其他应用原因PostThreadMessage的g_dwMainThreadId未正确初始化或钩子函数中g_dwMainThreadId为 0。解决在CMFCKeyMouseDlg::OnInitDialog()中确认赋值m_dwMainThreadId GetCurrentThreadId(); // 并在 KeyMouseHook.h 中声明 extern DWORD g_dwMainThreadId; // 在 KeyMouseHook.cpp 中定义DWORD g_dwMainThreadId 0;现象 3鼠标移动日志中pt.x/pt.y始终为 0原因MSLLHOOKSTRUCT结构体大小在 32/64 位下不同若头文件未正确包含winuser.h或#pragma pack设置错误会导致pt字段偏移错乱。解决确保LowLevelMouseProc.cpp顶部有#include windows.h且不要在项目设置中启用/Zp1结构体 1 字节对齐。现象 4程序退出时崩溃或卸载钩子后其他程序键盘失灵原因UninstallHook()中调用了UnhookWindowsHookEx(g_hHookKeyboard)但未置g_hHookKeyboard NULL导致后续CallNextHookEx传入无效句柄。解决卸载后立即清空句柄void UninstallHook() { if (g_hHookKeyboard) { UnhookWindowsHookEx(g_hHookKeyboard); g_hHookKeyboard NULL; // 关键 } if (g_hHookMouse) { UnhookWindowsHookEx(g_hHookMouse); g_hHookMouse NULL; } }现象 5在多显示器环境下鼠标坐标pt.x/pt.y超出单屏范围但日志显示异常大值如 10000原因MSLLHOOKSTRUCT::pt是屏幕坐标原点在主显示器左上角X 轴向右延伸Y 轴向下延伸。若副屏在主屏左侧pt.x可能为负若副屏在下方pt.y可能超主屏高度。这不是 Bug是正确行为。解决无需修复。若需归一化到某屏用MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST)获取当前显示器句柄再调用GetMonitorInfo获取边界。5. 二次开发指南从日志采集到行为分析的三步改造这个项目不是终点而是输入采集的起点。我曾基于它快速交付过三个生产需求某高校实验室的注意力监测系统统计学生切屏频率、某公司内部的远程支持工具实时显示客服鼠标轨迹、某无障碍项目的手势替代方案将鼠标双击映射为语音命令。下面分享可直接复用的改造路径每一步都附验证方法。5.1 第一步扩展事件类型 —— 支持组合键与长按检测原始代码只记录单次按键但真实场景需要知道 “CtrlC”、“AltTab” 或 “Shift 键持续按下 2 秒”。改造核心是维护一个键盘状态快照。实现方案在KeyMouseHook.h中添加全局状态数组// 全局键盘状态true按下false释放 extern bool g_bKeyState[256]; // 索引为 vkCode extern DWORD g_dwKeyStartTime[256]; // 每个键的按下起始时间在LowLevelKeyboardProc中更新状态if (wParam WM_KEYDOWN || wParam WM_SYSKEYDOWN) { g_bKeyState[vkCode] true; g_dwKeyStartTime[vkCode] GetTickCount(); } else if (wParam WM_KEYUP || wParam WM_SYSKEYUP) { g_bKeyState[vkCode] false; }在CMFCKeyMouseDlg::OnLogKey()中解析组合键// 检查 CtrlCvkCode67 且 Ctrl 键按下 if (vkCode C g_bKeyState[VK_CONTROL]) { strLog _T(CtrlC detected); } // 检查长按按下时间 2000ms if (g_dwKeyStartTime[vkCode] (GetTickCount() - g_dwKeyStartTime[vkCode] 2000)) { strLog _T( (long press)); }验证方法启动程序按住Shift键不放观察日志是否出现(long press)再按CtrlC确认日志显示CtrlC detected。无需重启程序热插拔生效。5.2 第二步持久化与导出 —— 将内存日志转为 CSV/JSON内存日志易丢失需支持导出。MFC 自带CStdioFile但为防编码问题推荐用 UTF-8 写 CSV。实现方案在对话框类中添加导出函数void CMFCKeyMouseDlg::ExportLogToCSV(const CString strPath) { CStdioFile file; if (!file.Open(strPath, CFile::modeCreate | CFile::modeWrite | CFile::typeText)) return; // 写 CSV 头 file.WriteString(_T(Time,Type,KeyCode,ScanCode,X,Y,Wheel\n)); // 遍历 m_listLog 所有项需先保存到 vector因 ListCtrl 不支持反向遍历 std::vectorCString logEntries; for (int i 0; i m_listLog.GetItemCount(); i) { CString strTime m_listLog.GetItemText(i, 0); // 假设第0列是时间 CString strType m_listLog.GetItemText(i, 1); // ... 其他列 logEntries.push_back(strTime _T(,) strType _T(\n)); } for (const auto entry : logEntries) { file.WriteString(entry); } file.Close(); }绑定到菜单或按钮调用时传入GetDesktopPath() _T(\\input_log.csv)。验证方法运行程序敲击 10 次键盘 移动鼠标 5 次点击“导出 CSV”。用 Excel 或 VS Code 打开确认首行是表头数据行格式为2023-01-01 12:00:00,Keyboard,65,30,,鼠标事件有 X,Y 值。5.3 第三步集成到其他框架 —— 将钩子逻辑封装为静态库若你的主项目是 Qt 或 C CLI不想拖 MFC 依赖可将钩子剥离为独立静态库.lib。改造步骤新建 Win32 Static Library 项目将KeyMouseHook.h/.cpp、LowLevelKeyboardProc.cpp、LowLevelMouseProc.cpp移入删除所有 MFC 相关头文件#include stdafx.h、#include afxdialogex.h改用#include windows.h将PostThreadMessage替换为回调函数指针在KeyMouseHook.h中定义typedef void (*LOG_CALLBACK)(int type, WPARAM wParam, LPARAM lParam); void SetLogCallback(LOG_CALLBACK cb); // 在 KeyMouseHook.cpp 中实现在钩子函数中调用g_pLogCallback(type, wParam, lParam)主程序Qt中实现该回调用QMetaObject::invokeMethod安全线程更新 UI。验证方法新建 Qt Widgets Application链接上述.lib在main()中调用InstallHook()并设置回调。运行后Qt 窗口应能实时显示键盘/鼠标事件证明剥离成功。从那以后我每次做输入采集类需求都强制走一遍“钩子安装→跨线程通信→权限验证→导出验证”四步 checklist。哪怕只是临时脚本也先搭好这个骨架——因为输入事件的可靠性永远排在功能炫酷之前。希望帮到你。本文还有配套的精品资源点击获取