Skip to content

Instantly share code, notes, and snippets.

@GageSorrell
Last active January 24, 2024 10:27
Show Gist options
  • Save GageSorrell/ec9c9bdec51c0107de1f2448194356d7 to your computer and use it in GitHub Desktop.
Save GageSorrell/ec9c9bdec51c0107de1f2448194356d7 to your computer and use it in GitHub Desktop.
Create a Windows API (Win32) message loop with the node-addon-api.
/* Gist: Win32 Message Loop with `node-addon-api`
* Author: Gage Sorrell <gsorrell@purdue.edu>
* Copyright: (c) 2023 Gage Sorrell
* License: MIT
*/
/* This file demonstrates how to get access to the Windows API (Win32)
* message loop in your NodeJS application via node-addon-api.
* This code goes into your node-addon-api package, with no additional
* build tools necessary.
*
* It works by creating an `Napi::AsyncProgressQueueWorker`, and upon
* initialization, creates a message-only window (a Win32 window that
* does not display anything and is not visible).
*
* To use this, copy the class into your code, copy the initialization
* function, and specify the function in your Exports. You may also
* specify a pointer somewhere in your code (as this gist does) so that
* you can alter the control flow, pass data (remember to use `std::atomic`),
* and specify additional callbacks. It's an open canvas.
*
* The class takes a template parameter for your data, but you may find
* it just as sensible to remove the template parameter, in which case
* you just fill in the template parameter for `AsyncProgressQueueWorker`.
*/
#include <Windows.h>
#include <napi.h>
#include <string>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Handle messages here...
// switch (uMsg)
// {
// case WM_DESTROY:
// return 0;
// }
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
template<typename T>
class FMessageLoop : public Napi::AsyncProgressQueueWorker<T>
{
public:
FMessageLoop(
Napi::Function& OkCallback,
Napi::Function& ErrorCallback,
Napi::Function& ProgressCallback,
Napi::Env& Environment
)
: Napi::AsyncProgressQueueWorker<T>(OkCallback)
, Environment(Environment)
, ShouldDisableKeyboardListening(false)
, ShouldEnableKeyboardListening(false)
{
this->ErrorCallback.Reset(ErrorCallback, 1);
this->ProgressCallback.Reset(ProgressCallback, 1);
}
~FMessageLoop() { }
virtual void Execute(const Napi::AsyncProgressQueueWorker<T>::ExecutionProgress& Progress) override
{
WNDCLASS WindowClass = { };
WindowClass.lpfnWndProc = WindowProc;
WindowClass.hInstance = GetModuleHandle(NULL);
WindowClass.lpszClassName = "MyClassName";
RegisterClass(&WindowClass);
HWND Handle = CreateWindowEx(
0,
WindowClass.lpszClassName,
"MyMessageWindow",
0,
0,
0,
0,
0,
HWND_MESSAGE,
nullptr,
WindowClass.hInstance,
nullptr
);
MSG Message;
while (GetMessage(&Message, NULL, 0, 0))
{
TranslateMessage(&Message);
DispatchMessage(&Message);
// Suppose we have a function `HandleMessage` defined elsewhere that
// accepts the message, and returns a value of type `T`, that we wish
// to send to our Node logic. Calling `Progress.Send` results in
// `OnProgress` being called, which lets us pass the data to Node.
//
// T OutData;
// HandleMessage(&Message, &OutData);
// Progress.Send(&OutData, 1);
}
}
virtual void OnOK() override
{
Napi::HandleScope Scope(Napi::Env());
if (!OkCallback.IsEmpty())
{
OkCallback.Call(
Receiver().Value(),
{ /* The arguments that you want to pass, as Napi values. */ }
);
}
}
virtual void OnError(const Napi::Error &Error)
{
Napi::HandleScope Scope(Napi::Env());
if (!ErrorCallback.IsEmpty())
{
ErrorCallback.Call(
Receiver().Value(),
{ /* The arguments that you want to pass, as Napi values. */ }
);
}
}
virtual void OnProgress(const T* Data, size_t Count) override
{
Napi::HandleScope Scope(Napi::Env());
if (!ProgressCallback.IsEmpty())
{
ProgressCallback.Call(
Receiver().Value(),
{ /* The arguments that you want to pass, as Napi values. */ }
);
}
}
private:
Napi::Env Environment;
Napi::FunctionReference ErrorCallback;
Napi::FunctionReference OkCallback;
Napi::FunctionReference ProgressCallback;
};
FMessageLoop<std::string>* MessageLoop = nullptr;
/**
* In the root of your node-addon-api package, create a file `index.d.ts` with these contents:
* ```
* export function InitializeMessageLoop(
* ErrorCallback: (OutError: Error) => void,
* OkCallback: () => void,
* ProgressCallback: (...Arguments: Array<unknown>) => void
* ): void;`
* ```
* Feel free to change the arguments of the ProgressCallback, based on your needs.
*/
Napi::Value InitializeMessageLoop(const Napi::CallbackInfo& Information)
{
Napi::Env Environment = Information.Env();
Napi::Function ErrorCallback = Information[0].As<Napi::Function>();
Napi::Function OkCallback = Information[1].As<Napi::Function>();
Napi::Function ProgressCallback = Information[2].As<Napi::Function>();
MessageLoop = new FMessageLoop<std::string>(
OkCallback,
ErrorCallback,
ProgressCallback,
Environment
);
MessageLoop->Queue();
return Environment.Undefined();
}
Napi::Object Init(Napi::Env Environment, Napi::Object Exports)
{
Exports.Set("InitializeMessageLoop", InitializeMessageLoop);
return Exports;
}
NODE_API_MODULE(YOUR_MODULE_NAME, Init)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment