如何在Node.js中使用koffi从lParam解析KBDLLHOOKSTRUCT?
问题描述
我正尝试使用koffi库将C键盘钩子程序转换为Node.js版本。在C中,通常会将lParam参数转换为KBDLLHOOKSTRUCT指针,代码如下:
KBDLLHOOKSTRUCT *pKeyBoard = (KBDLLHOOKSTRUCT *)lParam;
完整的C++代码如下:
#include <windows.h> #include <iostream> // Global variable to store the hook handle HHOOK hKeyboardHook; // Callback function for handling keyboard events LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { std::cout << nCode << std::endl; std::cout << wParam << std::endl; std::cout << lParam << std::endl; if (nCode >= 0) { // The keyboard message is being processed if (wParam == WM_KEYDOWN) { // A key was pressed down KBDLLHOOKSTRUCT *pKeyBoard = (KBDLLHOOKSTRUCT *)lParam; DWORD vkCode = pKeyBoard->vkCode; std::cout << "Key pressed: " << vkCode << std::endl; // If the ESC key is pressed, exit the application if (vkCode == VK_ESCAPE) { PostQuitMessage(0); } } } return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam); } int main() { // Set a global keyboard hook hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0); if (hKeyboardHook == NULL) { std::cerr << "Failed to install hook!" << std::endl; return 1; } std::cout << "Hook installed successfully!" << std::endl; // Message loop MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } // Unhook when no longer needed UnhookWindowsHookEx(hKeyboardHook); return 0; }
通过这种方式可以从KBDLLHOOKSTRUCT结构中获取虚拟键码及其他数据。
我尝试在Node.js中用koffi实现同样功能,但使用koffi.decode和koffi.as无法正确将lParam转换为KBDLLHOOKSTRUCT结构以访问数据,当前代码如下:
import koffi from "koffi" import * as D from "win32-def/def" const HHOOK = koffi.pointer("HHOOK", koffi.opaque()) const HINSTANCE = koffi.pointer("HINSTANCE", D.INT) const LRESULT = koffi.alias("LRESULT", D.LONG_PTR) const WPARAM = koffi.alias("WPARAM", D.ULONG_PTR) const LPARAM = koffi.alias("LPARAM", D.LONG_PTR) const KBDLLHOOKSTRUCT = koffi.struct("KBDLLHOOKSTRUCT", { vkCode: "DWORD", scanCode: "DWORD", flags: "DWORD", time: "DWORD", dwExtraInfo: "ULONG_PTR", }) export const HOOKPROC = koffi.proto("HOOKPROC", "LRESULT", ["int", "WPARAM", "LPARAM"]) export const SetWindowsHookExA = user32.func("SetWindowsHookExA", "HHOOK", [D.INT, koffi.pointer(HOOKPROC), HINSTANCE, "DWORD"]) const WH_KEYBOARD_LL = 13 export const installMouseHook = () => { if (!hHook) { const hInstance = GetModuleHandle(null) hHook = SetWindowsHookExA(WH_KEYBOARD_LL, onHookProc, null, 0) if (!hHook) { throw new Error("Failed to install keyboard hook") } console.log("Keyboard hook installed.") } } // Hook callback function const onHookProc = koffi.register((nCode, wParam, lParam) => { console.log(nCode, wParam, lParam) let kb = koffi.decode(lParam, "KBDLLHOOKSTRUCT") // <====not work let kb1= koffi.as(lParam, "KBDLLHOOKSTRUCT") //<====not work return CallNextHookEx(hHook, nCode, wParam, lParam) }, koffi.pointer(HOOKPROC))
解决方案
问题出在对lParam的处理方式上:在C++中lParam是指向KBDLLHOOKSTRUCT的指针,因此在koffi中需要先将lParam转换为对应结构体的指针类型,再解引用读取数据。
修正步骤
- 定义结构体指针类型:为
KBDLLHOOKSTRUCT创建指针类型,方便后续转换 - 正确转换并读取数据:在回调中用
koffi.as将lParam转为结构体指针,再通过koffi.deref解引用获取结构体实例 - 补充缺失API定义:完善
user32/kernel32模块、消息循环、核心Windows API的定义
完整修正代码
import koffi from "koffi"; import * as D from "win32-def/def"; // 加载系统动态链接库 const user32 = koffi.load("user32.dll"); const kernel32 = koffi.load("kernel32.dll"); // 定义类型别名与结构体 const HHOOK = koffi.pointer("HHOOK", koffi.opaque()); const HINSTANCE = koffi.pointer("HINSTANCE", koffi.opaque()); const LRESULT = koffi.alias("LRESULT", D.LONG_PTR); const WPARAM = koffi.alias("WPARAM", D.ULONG_PTR); const LPARAM = koffi.alias("LPARAM", D.LONG_PTR); const KBDLLHOOKSTRUCT = koffi.struct("KBDLLHOOKSTRUCT", { vkCode: "DWORD", scanCode: "DWORD", flags: "DWORD", time: "DWORD", dwExtraInfo: "ULONG_PTR", }); // 定义结构体指针类型 const PKBDLLHOOKSTRUCT = koffi.pointer(KBDLLHOOKSTRUCT); // 定义Windows API函数原型 const HOOKPROC = koffi.proto("HOOKPROC", "LRESULT", ["int", "WPARAM", "LPARAM"]); const SetWindowsHookExA = user32.func("SetWindowsHookExA", "HHOOK", [D.INT, koffi.pointer(HOOKPROC), HINSTANCE, "DWORD"]); const CallNextHookEx = user32.func("CallNextHookEx", "LRESULT", [HHOOK, "int", "WPARAM", "LPARAM"]); const GetModuleHandleA = kernel32.func("GetModuleHandleA", "HINSTANCE", ["const char *"]); const PostQuitMessage = user32.func("PostQuitMessage", "void", ["int"]); const GetMessageA = user32.func("GetMessageA", "BOOL", ["MSG *", "HWND", "UINT", "UINT"]); const TranslateMessage = user32.func("TranslateMessage", "BOOL", ["const MSG *"]); const DispatchMessageA = user32.func("DispatchMessageA", "LRESULT", ["const MSG *"]); const UnhookWindowsHookEx = user32.func("UnhookWindowsHookEx", "BOOL", [HHOOK]); // 常量定义 const WH_KEYBOARD_LL = 13; const WM_KEYDOWN = 0x0100; const VK_ESCAPE = 0x1B; let hHook = null; export const installKeyboardHook = () => { if (!hHook) { const hInstance = GetModuleHandleA(null); hHook = SetWindowsHookExA(WH_KEYBOARD_LL, onHookProc, hInstance, 0); if (!hHook) { throw new Error("Failed to install keyboard hook"); } console.log("Keyboard hook installed."); // 启动Windows消息循环(钩子运行的必要条件) const MSG = koffi.struct("MSG", { hwnd: "HWND", message: "UINT", wParam: "WPARAM", lParam: "LPARAM", time: "DWORD", pt: koffi.struct("POINT", { x: "LONG", y: "LONG" }) }); const msg = koffi.allocate(MSG); while (GetMessageA(msg, null, 0, 0)) { TranslateMessage(msg); DispatchMessageA(msg); } // 程序退出时卸载钩子 UnhookWindowsHookEx(hHook); } }; // 钩子回调函数 const onHookProc = koffi.register((nCode, wParam, lParam) => { console.log(nCode, wParam, lParam); if (nCode >= 0 && wParam === WM_KEYDOWN) { // 转换lParam为结构体指针并解引用 const kbPtr = koffi.as(lParam, PKBDLLHOOKSTRUCT); const kb = koffi.deref(kbPtr); console.log("Key pressed:", kb.vkCode); // 按下ESC键退出程序 if (kb.vkCode === VK_ESCAPE) { PostQuitMessage(0); } } return CallNextHookEx(hHook, nCode, wParam, lParam); }, koffi.pointer(HOOKPROC));
关键说明
koffi.as(lParam, PKBDLLHOOKSTRUCT):将数值型的lParam指针地址转换为KBDLLHOOKSTRUCT的指针对象koffi.deref(kbPtr):解引用指针,获取实际的结构体数据- 必须实现Windows消息循环,否则钩子无法正常接收事件
- 补充了所有缺失的系统API函数定义,确保代码能完整调用Windows系统接口
内容的提问来源于stack exchange,提问作者newer
相关产品推荐
相关产品推荐

