How do I pump window messages in a nodejs addon?
Asked Answered
P

3

11

In a Windows nodejs addon, I've created a window for the purpose of receiving messages.

Handle<Value> MakeMessageWindow(const Arguments &args) { // exposed to JS
    ...
    CreateWindow(L"ClassName", NULL, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, 0, 0);
    ...
}

I have a wndproc function.

Local<Function> wndProc;
LRESULT APIENTRY WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    // pack up the arguments into Local<Value> argv
    wndProc->Call(Context::GetCurrent()->Global(), 3, argv);
}

Now I need to pump messages. Normally, you'd do something like

MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) 
{
     TranslateMessage(&msg);
     DispatchMessage(&msg);
}

...but that won't work since it would just block the v8 event loop.

How do I pump Windows messages in a manner that won't block v8 and allows me to invoke a JS function when my window receives messages?

I presume libuv will play a role, but I'm unsure exactly how to safely invoke a JS function from C running on a separate thread, especially since uv_async_send is not guaranteed to invoke a callback every time you call it, and I need to ensure that my JS callback is called every time a window message is received.

Profile answered 19/7, 2013 at 2:4 Comment(0)
P
11

My mistake was trying to create the window on the V8 thread. Instead, uv_thread_create should be used to call a function that creates the window on a new thread and then proceeds to do its own message pump loop.

The wndproc function then needs to save received messages into a queue in a thread-safe manner and then use uv_async_send to notify the V8 thread that messages have arrived.

A function on the V8 thread (which was passed to uv_async_init) is then invoked after messages are enqueued. The function (thread-safely) pops each pending message off the queue and invokes the JS callback.

Profile answered 19/7, 2013 at 23:28 Comment(3)
Great info. Do you, by chance, have a repo or node module that accomplishes this? I'm looking to do the same thing.Guevara
Did your approach work? I'm having trouble passing callback function into uv_async_send, once the function uv_thread_create is called, I can't get hold of the callback function. Even persistent handles are not working out - I've posted that question in SO too.#31159529Westernmost
Do you have, or know of, a code repo that uses this technique? This is exactly the approach I am taking.Lamb
K
1

I needed to do this for Canon's EDSDK, which requires a message pump.

libuv's uv_idle_t is a good candidate for this:

Despite the name, idle handles will get their callbacks called on every loop iteration, not when the loop is actually “idle”

Example:

#include <uv.h>

uv_idle_t* idle = new uv_idle_t();
uv_idle_init(uv_default_loop(), idle);
uv_idle_start(idle, idle_winmsg);

void idle_winmsg (uv_idle_t* idle) {
    MSG msg;
    if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}
Kolyma answered 12/9, 2019 at 7:40 Comment(1)
Won't creating an idle handle cause libuv's I/O timeout to be set to 0, resulting in it eating through CPU time rather than sleeping for file or timer events? Is there not a way to hook Windows messages into an async handle so that libuv can properly sleep?Timothee
M
0

I found the actual reason for this.

The Node.js event loop will get stuck on polling for I/O, and given that there is no pending outstanding I/O operation, the event loop will go to sleep.

idle and prepare are run prior to the I/O polling that the Node.js event loop does but without any work scheduled you may see, at best, 1 or 2 callbacks.

There is no solution to this problem because there is no Win32 message API that can be used to signal an I/O completion port. You have to run the Win32 message loop in a separate thread.

Morose answered 27/7, 2018 at 8:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.