You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 07:24:56