在 Node.js C++ 插件中,永远不会调用 SetWindowsHookEx 的回调
In a Node.js C++ addon, SetWindowsHookEx's callback is never called
This is a simple Windows C++ keylogger example
当我 运行 它在 Visual Studio 中时,HookCallback
被正确调用。
我想在 Node.js 中使用 node-addon-api
做同样的事情,但我不想在文件中记录按键,我想将键码值发送到 JavaScript 世界使用回调。
Here's my repository。这就是我正在做的...
JavaScript
const addon = require("bindings")("push_to_talk");
addon.start((keyCode) => {
console.log("key is pressed:", keyCode);
});
console.log("testing...");
原生
#include <Windows.h>
#include <napi.h>
#include <time.h>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <sstream>
// Declare the TSFN
Napi::ThreadSafeFunction tsfn;
// Create a native callback function to be invoked by the TSFN
auto callback = [](Napi::Env env, Napi::Function jsCallback, int* value) {
// Call the JS callback
jsCallback.Call({Napi::Number::New(env, *value)});
// We're finished with the data.
delete value;
};
// variable to store the HANDLE to the hook. Don't declare it anywhere else then globally
// or you will get problems since every function uses this variable.
HHOOK _hook;
// This struct contains the data received by the hook callback. As you see in the callback function
// it contains the thing you will need: vkCode = virtual key code.
KBDLLHOOKSTRUCT kbdStruct;
// Trigger the JS callback when a key is pressed
void Start(const Napi::CallbackInfo& info) {
std::cout << "Start is called" << std::endl;
Napi::Env env = info.Env();
// Create a ThreadSafeFunction
tsfn = Napi::ThreadSafeFunction::New(
env,
info[0].As<Napi::Function>(), // JavaScript function called asynchronously
"Keyboard Events", // Name
0, // Unlimited queue
1 // Only one thread will use this initially
);
}
// This is the callback function. Consider it the event that is raised when, in this case,
// a key is pressed.
LRESULT __stdcall HookCallback(int nCode, WPARAM wParam, LPARAM lParam) {
std::cout << "HookCallback is called" << std::endl;
if (nCode >= 0) {
// the action is valid: HC_ACTION.
if (wParam == WM_KEYDOWN) {
// lParam is the pointer to the struct containing the data needed, so cast and assign it
// to kdbStruct.
kbdStruct = *((KBDLLHOOKSTRUCT*)lParam);
// Send (kbdStruct.vkCode) to JS world via "start" function callback parameter
int* value = new int(kbdStruct.vkCode);
napi_status status = tsfn.BlockingCall(value, callback);
if (status != napi_ok) {
std::cout << "BlockingCall is not ok" << std::endl;
}
}
}
// call the next hook in the hook chain. This is nessecary or your hook chain will break and the
// hook stops
return CallNextHookEx(_hook, nCode, wParam, lParam);
}
void SetHook() {
std::cout << "SetHook is called" << std::endl;
// Set the hook and set it to use the callback function above
// WH_KEYBOARD_LL means it will set a low level keyboard hook. More information about it at
// MSDN. The last 2 parameters are NULL, 0 because the callback function is in the same thread
// and window as the function that sets and releases the hook.
if (!(_hook = SetWindowsHookEx(WH_KEYBOARD_LL, HookCallback, NULL, 0))) {
LPCSTR a = "Failed to install hook!";
LPCSTR b = "Error";
MessageBox(NULL, a, b, MB_ICONERROR);
}
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "start"), Napi::Function::New(env, Start));
// set the hook
SetHook();
return exports;
}
NODE_API_MODULE(push_to_talk, Init)
但是,在我的情况下,HookCallback
从未被调用(HookCallback is called
消息从未被打印),当我点击键盘时,点击速度变慢,我的延迟非常明显出于某种原因。
Update:根据 LowLevelKeyboardProc
documentation:“此挂钩在安装它的线程的上下文中调用。调用是通过向安装钩子的线程。因此,安装钩子的线程必须有一个消息循环。"
我试过像这样循环调用 GetMessage
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "start"), Napi::Function::New(env, Start));
// set the hook
SetHook();
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
if (bRet == -1) {
// handle the error and possibly exit
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return exports;
}
但这会阻塞 JavaScript 线程。此外,当现在按下键盘按钮时,调试消息 HookCallback is called
实际上会打印出来,但随后在这一行 while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
...
发生崩溃
$ node .
SetHook is called
HookCallback is called
C:\Windows\SYSTEM32\cmd.exe - node .[10888]: c:\ws\src\node_api.cc:1078: Assertion `(func) != nullptr' failed.
1: 77201783 RegisterLogonProcess+3427
2: 77DC537D KiUserCallbackDispatcher+77
3: 607007B9 Init+521 [c:\users\aabuhijleh\desktop\projects\testing\push-to-talk\src\push-to-talk.cc]:L93
通过在单独的线程中创建消息循环
,我能够使它像看到的那样工作here
// Trigger the JS callback when a key is pressed
void Start(const Napi::CallbackInfo& info) {
std::cout << "Start is called" << std::endl;
Napi::Env env = info.Env();
// Create a ThreadSafeFunction
tsfn = Napi::ThreadSafeFunction::New(
env,
info[0].As<Napi::Function>(), // JavaScript function called asynchronously
"Keyboard Events", // Name
0, // Unlimited queue
1, // Only one thread will use this initially
[](Napi::Env) { // Finalizer used to clean threads up
nativeThread.join();
});
nativeThread = std::thread([] {
// This is the callback function. Consider it the event that is raised when, in this case,
// a key is pressed.
static auto HookCallback = [](int nCode, WPARAM wParam, LPARAM lParam) -> LRESULT {
if (nCode >= 0) {
// the action is valid: HC_ACTION.
if (wParam == WM_KEYDOWN) {
// lParam is the pointer to the struct containing the data needed, so cast and
// assign it to kdbStruct.
kbdStruct = *((KBDLLHOOKSTRUCT*)lParam);
// Send (kbdStruct.vkCode) to JS world via "start" function callback parameter
int* value = new int(kbdStruct.vkCode);
napi_status status = tsfn.BlockingCall(value, callback);
if (status != napi_ok) {
std::cout << "BlockingCall is not ok" << std::endl;
}
}
}
// call the next hook in the hook chain. This is nessecary or your hook chain will
// break and the hook stops
return CallNextHookEx(_hook, nCode, wParam, lParam);
};
// Set the hook and set it to use the callback function above
// WH_KEYBOARD_LL means it will set a low level keyboard hook. More information about it at
// MSDN. The last 2 parameters are NULL, 0 because the callback function is in the same
// thread and window as the function that sets and releases the hook.
if (!(_hook = SetWindowsHookEx(WH_KEYBOARD_LL, HookCallback, NULL, 0))) {
LPCSTR a = "Failed to install hook!";
LPCSTR b = "Error";
MessageBox(NULL, a, b, MB_ICONERROR);
}
// Create a message loop
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
if (bRet == -1) {
// handle the error and possibly exit
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
});
}
现在 JavaScript 回调在按下键盘时正确调用。
This is a simple Windows C++ keylogger example
当我 运行 它在 Visual Studio 中时,HookCallback
被正确调用。
我想在 Node.js 中使用 node-addon-api
做同样的事情,但我不想在文件中记录按键,我想将键码值发送到 JavaScript 世界使用回调。
Here's my repository。这就是我正在做的...
JavaScript
const addon = require("bindings")("push_to_talk");
addon.start((keyCode) => {
console.log("key is pressed:", keyCode);
});
console.log("testing...");
原生
#include <Windows.h>
#include <napi.h>
#include <time.h>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <sstream>
// Declare the TSFN
Napi::ThreadSafeFunction tsfn;
// Create a native callback function to be invoked by the TSFN
auto callback = [](Napi::Env env, Napi::Function jsCallback, int* value) {
// Call the JS callback
jsCallback.Call({Napi::Number::New(env, *value)});
// We're finished with the data.
delete value;
};
// variable to store the HANDLE to the hook. Don't declare it anywhere else then globally
// or you will get problems since every function uses this variable.
HHOOK _hook;
// This struct contains the data received by the hook callback. As you see in the callback function
// it contains the thing you will need: vkCode = virtual key code.
KBDLLHOOKSTRUCT kbdStruct;
// Trigger the JS callback when a key is pressed
void Start(const Napi::CallbackInfo& info) {
std::cout << "Start is called" << std::endl;
Napi::Env env = info.Env();
// Create a ThreadSafeFunction
tsfn = Napi::ThreadSafeFunction::New(
env,
info[0].As<Napi::Function>(), // JavaScript function called asynchronously
"Keyboard Events", // Name
0, // Unlimited queue
1 // Only one thread will use this initially
);
}
// This is the callback function. Consider it the event that is raised when, in this case,
// a key is pressed.
LRESULT __stdcall HookCallback(int nCode, WPARAM wParam, LPARAM lParam) {
std::cout << "HookCallback is called" << std::endl;
if (nCode >= 0) {
// the action is valid: HC_ACTION.
if (wParam == WM_KEYDOWN) {
// lParam is the pointer to the struct containing the data needed, so cast and assign it
// to kdbStruct.
kbdStruct = *((KBDLLHOOKSTRUCT*)lParam);
// Send (kbdStruct.vkCode) to JS world via "start" function callback parameter
int* value = new int(kbdStruct.vkCode);
napi_status status = tsfn.BlockingCall(value, callback);
if (status != napi_ok) {
std::cout << "BlockingCall is not ok" << std::endl;
}
}
}
// call the next hook in the hook chain. This is nessecary or your hook chain will break and the
// hook stops
return CallNextHookEx(_hook, nCode, wParam, lParam);
}
void SetHook() {
std::cout << "SetHook is called" << std::endl;
// Set the hook and set it to use the callback function above
// WH_KEYBOARD_LL means it will set a low level keyboard hook. More information about it at
// MSDN. The last 2 parameters are NULL, 0 because the callback function is in the same thread
// and window as the function that sets and releases the hook.
if (!(_hook = SetWindowsHookEx(WH_KEYBOARD_LL, HookCallback, NULL, 0))) {
LPCSTR a = "Failed to install hook!";
LPCSTR b = "Error";
MessageBox(NULL, a, b, MB_ICONERROR);
}
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "start"), Napi::Function::New(env, Start));
// set the hook
SetHook();
return exports;
}
NODE_API_MODULE(push_to_talk, Init)
但是,在我的情况下,HookCallback
从未被调用(HookCallback is called
消息从未被打印),当我点击键盘时,点击速度变慢,我的延迟非常明显出于某种原因。
Update:根据 LowLevelKeyboardProc
documentation:“此挂钩在安装它的线程的上下文中调用。调用是通过向安装钩子的线程。因此,安装钩子的线程必须有一个消息循环。"
我试过像这样循环调用 GetMessage
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "start"), Napi::Function::New(env, Start));
// set the hook
SetHook();
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
if (bRet == -1) {
// handle the error and possibly exit
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return exports;
}
但这会阻塞 JavaScript 线程。此外,当现在按下键盘按钮时,调试消息 HookCallback is called
实际上会打印出来,但随后在这一行 while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
...
$ node .
SetHook is called
HookCallback is called
C:\Windows\SYSTEM32\cmd.exe - node .[10888]: c:\ws\src\node_api.cc:1078: Assertion `(func) != nullptr' failed.
1: 77201783 RegisterLogonProcess+3427
2: 77DC537D KiUserCallbackDispatcher+77
3: 607007B9 Init+521 [c:\users\aabuhijleh\desktop\projects\testing\push-to-talk\src\push-to-talk.cc]:L93
通过在单独的线程中创建消息循环
,我能够使它像看到的那样工作here// Trigger the JS callback when a key is pressed
void Start(const Napi::CallbackInfo& info) {
std::cout << "Start is called" << std::endl;
Napi::Env env = info.Env();
// Create a ThreadSafeFunction
tsfn = Napi::ThreadSafeFunction::New(
env,
info[0].As<Napi::Function>(), // JavaScript function called asynchronously
"Keyboard Events", // Name
0, // Unlimited queue
1, // Only one thread will use this initially
[](Napi::Env) { // Finalizer used to clean threads up
nativeThread.join();
});
nativeThread = std::thread([] {
// This is the callback function. Consider it the event that is raised when, in this case,
// a key is pressed.
static auto HookCallback = [](int nCode, WPARAM wParam, LPARAM lParam) -> LRESULT {
if (nCode >= 0) {
// the action is valid: HC_ACTION.
if (wParam == WM_KEYDOWN) {
// lParam is the pointer to the struct containing the data needed, so cast and
// assign it to kdbStruct.
kbdStruct = *((KBDLLHOOKSTRUCT*)lParam);
// Send (kbdStruct.vkCode) to JS world via "start" function callback parameter
int* value = new int(kbdStruct.vkCode);
napi_status status = tsfn.BlockingCall(value, callback);
if (status != napi_ok) {
std::cout << "BlockingCall is not ok" << std::endl;
}
}
}
// call the next hook in the hook chain. This is nessecary or your hook chain will
// break and the hook stops
return CallNextHookEx(_hook, nCode, wParam, lParam);
};
// Set the hook and set it to use the callback function above
// WH_KEYBOARD_LL means it will set a low level keyboard hook. More information about it at
// MSDN. The last 2 parameters are NULL, 0 because the callback function is in the same
// thread and window as the function that sets and releases the hook.
if (!(_hook = SetWindowsHookEx(WH_KEYBOARD_LL, HookCallback, NULL, 0))) {
LPCSTR a = "Failed to install hook!";
LPCSTR b = "Error";
MessageBox(NULL, a, b, MB_ICONERROR);
}
// Create a message loop
MSG msg;
BOOL bRet;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
if (bRet == -1) {
// handle the error and possibly exit
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
});
}
现在 JavaScript 回调在按下键盘时正确调用。