Last active
January 24, 2024 10:27
-
-
Save GageSorrell/ec9c9bdec51c0107de1f2448194356d7 to your computer and use it in GitHub Desktop.
Create a Windows API (Win32) message loop with the node-addon-api.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Gist: Win32 Message Loop with `node-addon-api` | |
* Author: Gage Sorrell <[email protected]> | |
* 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