Rohan Rohan - 2 months ago 19
C++ Question

SetWindowsHookEx in a node.js module using node-nan

I'm trying to create an Electron node.js application that can perform certain functions using global keybindings. The global keybindings API in Electron doesn't work in games unfortunately so I need to create a native node module that listens to these low level key events.

So i'm using node-gyp to compile the project with visual studio 2015 and nan to provide the communication between node and c++. I've managed to get both aspects of the project working seperately(the low level key bindings and the node.js<-->nan communication) but I'm having trouble combining them. I also admit I have very little experience with c++(I haven't written a single c++ program).

#include "node_modules/nan/nan.h"

using namespace std;
using namespace Nan;

HHOOK _hook;
KBDLLHOOKSTRUCT kbdStruct;

class KeyboardEventWorker : public AsyncProgressWorker {
public:
KeyboardEventWorker(Callback *callback, Callback *progress)
: AsyncProgressWorker(callback), progress(progress) {}
~KeyboardEventWorker() {}

LRESULT CALLBACK HookCallback(int nCode,WPARAM wParam,LPARAM lParam) {
executionProgress->Send(reinterpret_cast<const char*>(nCode), sizeof(nCode));
return CallNextHookEx(_hook, nCode, wParam, lParam);
}

void Execute (const AsyncProgressWorker::ExecutionProgress& progress) {
executionProgress = &progress; //PROBLEM #1
_hook = SetWindowsHookEx(13, HookCallback, NULL, 0); //PROBLEM #2

SleepEx(INFINITE, true);
}


void HandleProgressCallback(const char *data, size_t size) {
HandleScope scope;

v8::Local<v8::Value> argv[] = {
New<v8::Integer>(*reinterpret_cast<int*>(const_cast<char*>(data)))
};
progress->Call(1, argv);
}

private:
Callback *progress;
AsyncProgressWorker::ExecutionProgress *executionProgress;
};

NAN_METHOD(DoProgress) {
Callback *progress = new Callback(info[0].As<v8::Function>());
Callback *callback = new Callback(info[1].As<v8::Function>());
AsyncQueueWorker(new KeyboardEventWorker(callback, progress));
}

NAN_MODULE_INIT(Init) {
Set(target
, New<v8::String>("init").ToLocalChecked()
, New<v8::FunctionTemplate>(DoProgress)->GetFunction());
}

NODE_MODULE(asyncprogressworker, Init)


Problem #1: To be able to send back messages to node.js, I'l need to copy the pointer of AsyncProgressWorker::ExecutionProgress and make it available to the entire class so when HookCallback triggers it can send a message to node.js.

The compiler doesn't like this


..\binding.cc(21): error C2440: '=': cannot convert from 'const
Nan::AsyncProgressWorker::ExecutionProgress *' to 'Nan:
:AsyncProgressWorker::ExecutionProgress *'
[C:\Users\eksrow\gdrive\projects\vscode\node-native-hello-world\build\bindin
g.vcxproj].

..\binding.cc(21): note: Conversion loses qualifiers


Formatted:


'const Nan::AsyncProgressWorker::ExecutionProgress *'

'Nan::AsyncProgressWorker::ExecutionProgress *'


This one I managed to solve by adding the keyword const to private member *executionProgress;. But I don't understand why that would fix it, const variables aren't supposed to be changed once they are set. Why does this compile?

Problem #2: This one is very peculiar:


..\binding.cc(22): error C3867: 'KeyboardEventWorker::HookCallback':
non-standard syntax; use '&' to create a pointer t o member
[C:\Users\eksrow\gdrive\projects\vscode\node-native-hello-world\build\binding.vcxproj]


I've looked up many examples online and they all have the same syntax regarding this:


  1. SetWindowsHookEx #1

  2. SetWindowsHookEx #2



I can't see the difference between my code and theirs regarding that line.

If I do what the compiler says and add an ampersand to that line, it gives a completely different error:


..\binding.cc(22): error C2276: '&': illegal operation on bound member
function expression [C:\Users\eksrow\gdrive\proj
ects\vscode\node-native-hello-world\build\binding.vcxproj]
..\binding.cc(22): error C2660: 'SetWindowsHookExA': function does not
take 3 arguments [C:\Users\eksrow\gdrive\project
s\vscode\node-native-hello-world\build\binding.vcxproj]

Answer

For Problem #1, you correctly identified the const qualifier as the problem.

The reason you can assign to the const member variable after declaring it is because of the placement of const in const AsyncProgressWorker::ExecutionProgress *executionProgress. This is a variable pointer to a constant AsyncProgressWorker::ExecutionProgress. This means you can change the pointer's value (for instance, re-assign it as in your example above), but you cannot change the data it points to. The top answer of this question has a very good explanation of this concept.

For problem #2, the error is caused by trying to pass a member function of your class as a function callback. This is simply not possible (well, without a workaround...see below) - a member method is not the same as a function, which is what SetWindowsHookEx is expecting. You could make your callback function static, but then would not be able to access the _hook member.

Here is an external page with a (very hack-y) workaround for this exact problem. It should allow you to use SetWindowsHookEx in the way you're currently trying to use it. However, I would recommend re-thinking the way your application is hooking and instead see if there is a way to register a single global function or static member function as your callback for your application.

Unrelated to your question: unless you've omitted code in your sample, you never unhook the hook that is set in SetWindowsHookEx. Take a look at the MSDN for UnhookWindowsHookEx.