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。哪怕只是临时脚本也先搭好这个骨架——因为输入事件的可靠性永远排在功能炫酷之前。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

AI写论文的正确打开方式:人机协作流程与文献真实性避坑指南

AI写论文的正确打开方式:人机协作流程与文献真实性避坑指南

又到论文季了。朋友圈、群聊、各种收藏夹里,又开始飘起“AI写论文”“一键生成”“真实参考文献”这些词。我见过太多人被“10分钟生成万字”这种标题吸引点进去,然后拿着一堆AI吐出来的空话和假文献,在DDL前夜手忙脚乱。今天这篇&#xff0c…

2026/10/18 10:39:49 阅读更多 →
从GPT到智能体:核心架构、落地实践与避坑指南

从GPT到智能体:核心架构、落地实践与避坑指南

1. 从GPT到智能体,本质是“能力半径”的跃迁很多人可能还记得第一次打开那款对话模型时的感觉:你问一句,它答一段,几乎什么都知道,那种体验确实让人佩服。但用着用着,一个尴尬的事实就浮现出来了——大模型…

2026/10/18 10:39:49 阅读更多 →
Servlet+JSP+JDBC房屋租赁管理系统:从HTTP请求到SQL的完整链路解析

Servlet+JSP+JDBC房屋租赁管理系统:从HTTP请求到SQL的完整链路解析

简介:这套基于ServletJsp的房屋租赁管理系统,面向计算机相关专业正在做毕业设计的学生以及需要项目实战的Java学习者,解决从需求分析到系统实现的一站式参考需求。系统功能覆盖会员登录、免费注册、信息检索、新闻中心、留言板、房源发布&…

2026/10/18 10:39:49 阅读更多 →

最新新闻

Flink实时计算三要素:Window、State与Checkpoint深度解析

Flink实时计算三要素:Window、State与Checkpoint深度解析

做实时计算绕不开这几个词:Window、State、Checkpoint。不管你是刚接触Flink,还是已经写了一段时间作业,总会遇到数据算不准、重启后状态丢了、窗口边界对不齐这类问题。我见过很多新手一上来就写业务代码,结果窗口和状态混在一起…

2026/10/18 11:32:06 阅读更多 →
YOLOv12即插即用模块化改进:注意力机制与跨尺度融合实战

YOLOv12即插即用模块化改进:注意力机制与跨尺度融合实战

1. 模块即插即用的整体设计思路1.1 为什么我坚持用模块替换而不是重构整个网络YOLOv12 的改进工作做到第三部分,先说一下我的总体判断:如果你不是做学术研究、不是想发论文,而是手上有一个实际项目要落地,最好的做法不是把网络结构…

2026/10/18 11:32:06 阅读更多 →
AI信息源清单与100美元低成本训练:一份可落地的技术路径指南

AI信息源清单与100美元低成本训练:一份可落地的技术路径指南

最近在 GitHub 上扒到一份挺有意思的开源收藏:92 个 AI 信息源被整理成一份清单,论文、开源仓库、Newsletter、播客、社区讨论全都收进去了。我第一次刷完这份清单的感觉是“这也太干了”,但真正用了一段时间后,我意识到值钱的不是…

2026/10/18 11:32:06 阅读更多 →
高校统一身份认证系统设计:CAS单点登录与安全实现指南

高校统一身份认证系统设计:CAS单点登录与安全实现指南

1. 为什么要做高校统一身份认证前阵子有个A同学抱着毕设题目来问我:“Java写一个高校统一身份认证系统,到底怎么做才是对的?”我一看题目就明白了,这几乎是每届计算机毕设的常客。做的人多,但真正把“统一身份认证”这…

2026/10/18 11:32:06 阅读更多 →
Java实现TRC20收款系统:从区块扫描到幂等回调的完整实践

Java实现TRC20收款系统:从区块扫描到幂等回调的完整实践

简介:这是一套基于Java开发的TRC20收款系统完整源码包,面向Java后端开发者与区块链应用集成人员,可用于快速搭建支持USDT(TRC20)收款的业务平台。资源共377个文件,压缩包仅6.15MB,结构清晰&…

2026/10/18 11:32:06 阅读更多 →
LangChain SQL Agent实战:构建企业级自然语言查数助手

LangChain SQL Agent实战:构建企业级自然语言查数助手

说实话,直到今天,仍然有大量团队在“让大模型直接写SQL”这件事上反复踩坑。裸调大模型接口,让它根据一句“帮我查一下上个月华东区哪些客户的回款逾期了”生成SQL,然后把结果丢给用户——你很快会发现几类问题:生成的…

2026/10/18 11:31:06 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →