hello_gui and cleanup_gui lifecycle management
hello_gui() initializes GTK, creates main context and loop, launches detached thread running g_main_loop_run(), and checks if GTK already running to prevent double initialization. cleanup_gui() quits the main loop if running, unreferences the main loop and main context, and cleans up the thread pointer. Proper cleanup prevents resource leaks and allows clean application shutdown.
Node.js addon bridge class structure with Napi::ObjectWrap
Create CppAddon class inheriting from Napi::ObjectWrap<CppAddon>. Implement static Init() method using DefineClass to expose instance methods: helloWorld, helloGui, on, destroy. Store Napi::Env, ObjectReferences for emitter and callbacks, and napi_threadsafe_function pointer. The destructor must release the thread-safe function with napi_release_threadsafe_function(tsfn_, napi_tsfn_release).
napi_create_threadsafe_function for GTK3 thread bridge
Use napi_create_threadsafe_function to safely call JavaScript from GTK3 thread. The function queues calls on JavaScript thread and handles resource management. Parameters: env, NULL for js_callback, NULL for async resource, name string ("CppCallback"), initial thread count (0), max queue size (1), finalize callback (NULL), finalize data (NULL), context pointer (this), callback function to execute JS code, output pointer for tsfn. The callback receives the event type and payload, retrieves the corresponding JavaScript function from callbacks map, and calls it with payload as argument.
Node.js native addon HelloWorld method implementation
Implement HelloWorld() method in CppAddon: validate that argument 0 exists and is a string (throw TypeError if not), convert to std::string, call cpp_code::hello_world(input), return result as Napi::String::New(env, result).
Node.js native addon On method for callback registration
Implement On() method in CppAddon: validate that argument 0 is a string and argument 1 is a function (throw TypeError if not), store the function in callbacks map using callbacks.Value().Set(eventType, functionValue), return env.Undefined(). This allows JavaScript to register event callbacks that will be invoked when events occur.
Linux build tools and dependencies for native Electron addons
To build a native C++ Linux addon for Electron using GTK3, install: (1) A Linux distribution with GTK3 development files installed; (2) The pkg-config tool from https://www.freedesktop.org/wiki/Software/pkg-config/; (3) G++ compiler and build tools. On Ubuntu/Debian: sudo apt-get install build-essential pkg-config libgtk-3-dev. On Fedora/RHEL/CentOS: sudo dnf install gcc-c++ pkgconfig gtk3-devel.
Node.js native addon callback setup with makeCallback lambda
In CppAddon constructor, create makeCallback lambda that takes eventType string and returns a callback function. This returned function, when called with payload, creates a CallbackData struct, and calls napi_call_threadsafe_function(tsfn_, data, napi_tsfn_blocking) to queue execution on JavaScript thread. Use makeCallback to set three C++ callbacks: cpp_code::setTodoAddedCallback(makeCallback("todoAdded")), setTodoUpdatedCallback(makeCallback("todoUpdated")), setTodoDeletedCallback(makeCallback("todoDeleted")).
Thread-safe function callback execution flow
The thread-safe function callback: (1) receives CallbackData with eventType and JSON payload; (2) creates Napi::Env and HandleScope for safe V8 access; (3) retrieves the JavaScript callback from addon->callbacks map using eventType as key; (4) calls the function with addon->emitter as this context and payload string as argument; (5) cleans up CallbackData. Wrapped in try-catch to handle errors safely without crashing.
Destroy method for cleanup before app exit
Implement Destroy() method in CppAddon: reset callbacks ObjectReference, reset emitter ObjectReference, release thread-safe function with napi_release_threadsafe_function(tsfn_, napi_tsfn_abort), set tsfn_ to nullptr, return env.Undefined(). This ensures all persistent references are released before application termination to prevent crashes or memory leaks.
Napi::CallbackInfo parameter handling
Napi::CallbackInfo from node-addon-api encapsulates JavaScript function call information: info.Env() gets JavaScript execution environment, info.Length() gets number of arguments, info[index] accesses arguments, info[index].IsString()/IsFunction() type checks, info[index].As<Napi::String>()/As<Napi::Function>() type conversions. Every native method callable from JavaScript receives a CallbackInfo object to access and validate arguments.
N-API documentation references for thread-safe functions
Official resources for native addon development: (1) N-API documentation: https://nodejs.org/api/n-api.html#n_api_napi_create_threadsafe_function for detailed thread-safe function information; (2) node-addon-api wrapper: https://github.com/nodejs/node-addon-api/blob/main/doc/threadsafe_function.md for C++ wrapper implementation; (3) Node.js Threading Model: https://nodejs.org/en/docs/guides/dont-block-the-event-loop/ for understanding JavaScript event loop concurrency.
Node.js addon folder structure for Linux GTK3 integration
Structure a Node.js native addon package with: (1) binding.gyp - configuration file for node-gyp to build the native addon; (2) include/cpp_code.h - header file with C++ declarations; (3) js/index.js - JavaScript interface that loads and exposes the addon; (4) package.json - Node.js package configuration; (5) src/cpp_addon.cc - C++ bridge code between Node.js/Electron and native code; (6) src/cpp_code.cc - C++ implementation using GTK3.
package.json for native Linux GTK3 addon
Configure package.json with: name, version (e.g. "1.0.0"), description, main pointing to js/index.js, scripts including "clean": "rm -rf build", "build-electron": "electron-rebuild", "build": "node-gyp configure && node-gyp build", license, and dependencies including "node-addon-api": "^8.3.0" and "bindings": "^1.5.0".
binding.gyp conditional compilation for Linux GTK3
The binding.gyp file uses command expansion with <!@(command) syntax to execute shell commands and use their output. For Linux GTK3 addon: (1) Wrap target configuration in conditions ['OS=="linux"', {...}] to compile only on Linux; (2) Use <!@(pkg-config --cflags-only-I gtk+-3.0 | sed s/-I//g) for include directories; (3) Use <!@(pkg-config --libs gtk+-3.0) and "-luuid" for libraries; (4) Set cflags and cflags_cc to include "-fexceptions", "<!@(pkg-config --cflags gtk+-3.0)", and "-pthread"; (5) Set ldflags to "-pthread"; (6) Override default exceptions with "cflags!": ["-fno-exceptions"] and "cflags_cc!": ["-fno-exceptions"]; (7) Define "NODE_ADDON_API_CPP_EXCEPTIONS"; (8) Include node-addon-api gyp file in dependencies.
TodoItem struct for native GTK3 addon
Implement a TodoItem struct containing: (1) uuid_t id - unique identifier; (2) std::string text - todo text content; (3) int64_t date - timestamp in milliseconds. Include methods: toJson() - returns JSON string representation with id, text, and date; formatDate(int64_t timestamp) - static helper that converts milliseconds timestamp to "YYYY-MM-DD" format.
GTK3 thread management in native Electron addon
Run GTK3 in a separate thread from Electron's main thread. Store references to: (1) g_gtk_main_context - the GTK main context; (2) g_main_loop - the GTK main loop; (3) g_gtk_thread - pointer to the separate thread. This prevents the GTK main loop from blocking the JavaScript event loop. The hello_gui() function creates a new main context with g_main_context_new(), a new main loop with g_main_loop_new(), and launches a detached std::thread that runs g_main_loop_run().
notify_callback thread-safe callback invocation
Use g_main_context_invoke() to safely invoke JavaScript callbacks from the GTK3 thread. The function accepts a callback and JSON string payload, checks if both exist and g_gtk_main_context is set, then calls g_main_context_invoke with a lambda that unpacks the data and invokes the callback. This ensures thread safety by scheduling the function execution in the GTK main context.
GTK3 event handlers for todo application
Implement event handlers: (1) edit_action - gets selected row, creates dialog with existing todo data, updates if confirmed, notifies callback; (2) delete_action - removes todo from list and notifies callback; (3) on_add_clicked - creates new TodoItem with UUID, extracts text and date from inputs, adds to list, clears input, notifies callback; (4) on_row_activated - creates popup menu with Edit and Delete options. All handlers should notify JavaScript callbacks with JSON payload.
GTK3 application setup using activate_handler
The activate_handler registers edit and delete GActions, builds UI from XML markup defining a window (title "Todo List", 400x500 default size) with vertical box layout containing: (1) top box with text entry (id="todo_entry", placeholder "Enter todo item..."), calendar widget (id="todo_calendar"), and Add button (id="add_button"); (2) scrolled window containing list box (id="todo_list") with single selection mode. Connect signals: button "clicked" to on_add_clicked, list "row-activated" to on_row_activated.
GTK3 requirement for Linux native Electron addons
Use GTK3 instead of GTK4 in native Linux code for Electron. Chromium (and by extension Electron) uses GTK3 internally. Using GTK4 would cause runtime conflicts because both GTK3 and GTK4 would be loaded in the same process. When Chromium upgrades to GTK4, native code can likely be easily upgraded to GTK4 as well.
Generate GUID in C++ with CoCreateGuid
Generate a unique GUID for an item with CoCreateGuid(&guidVariable). Convert GUID to string with StringFromCLSID(guid, &guidString) which returns an OLECHAR pointer. Free the memory with CoTaskMemFree(guidString).
Node-API ObjectWrap for C++ class wrapping
Inherit from Napi::ObjectWrap<YourClass> to wrap a C++ class for JavaScript. Use DefineClass() to define class name and instance methods as an array of InstanceMethod entries. Each method is a pointer to a member function returning Napi::Value or void. Store a Napi::FunctionReference constructor and set it on the environment with env.SetInstanceData().
Node-API callback registration method
Implement an 'on' method that validates string event name and function callback: if (info.Length() < 2 || !info[0].IsString() || !info[1].IsFunction()) throw TypeError. Store callbacks in a persistent object: callbacks.Value().Set(eventName, callbackFunction).
JavaScript wrapper for native addon
Create a JavaScript wrapper class extending EventEmitter that: (1) loads the native addon with require('bindings')('addon_name'), (2) instantiates the native class, (3) registers native addon callbacks that re-emit as JavaScript events, (4) provides convenience methods that call native addon methods, (5) performs data transformation (e.g., converting timestamps to JavaScript Date objects). Check process.platform === 'win32' before loading.
Node-API exception handling in C++
Throw JavaScript exceptions from C++ with Napi::TypeError::New(env, "message").ThrowAsJavaScriptException(). Define NODE_ADDON_API_CPP_EXCEPTIONS in binding.gyp to enable C++ exception throwing instead of return-value error patterns.
Win32 manifest dependency for Common Controls
Link the Common Controls manifest with: #pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\""). This ensures the correct version of Common Controls library is loaded.
Napi types for Node-API method parameters
Extract parameters from Napi::CallbackInfo with: info[index].As<Napi::String>() for strings, info[index].As<Napi::Function>() for functions, info[index].IsString() and info[index].IsFunction() to check types before casting. Return values using Napi::String::New(env, string), Napi::Number::New(env, number), or env.Null(), env.Undefined().
NODE_API_MODULE macro registers addon entry point
At the end of the addon file, use NODE_API_MODULE(addon_name, Init) where addon_name matches the name in binding.gyp target_name and Init is the initialization function that returns the exports object.
npm script for building native addon
Add to package.json scripts: "build": "node-gyp configure && node-gyp build". For Electron, also add "build-electron": "electron-rebuild" to rebuild the addon for the Electron Node version.
Package.json dependencies for Node-API addon
Include in package.json dependencies: "bindings": "^1.5.0" for loading native modules and "node-addon-api": "^8.3.0" for Node-API headers. The binding.gyp should reference node-addon-api with require('node-addon-api').include for include_dirs and require('node-addon-api').gyp in dependencies.
Enable Per-Monitor DPI awareness for Win32 GUI
Call SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) before creating windows to ensure proper display scaling on high-DPI monitors. Use the Scale() helper function with MulDiv(value, dpi, 96) to scale UI element sizes based on the system DPI.
Windows native addon requires separate GUI thread
The Windows message loop runs indefinitely, so a native Win32 GUI must run on a separate thread to avoid blocking the Node.js event loop. Use std::thread to launch the GUI in a separate thread and call detach() to let it run independently.
Initialize Common Controls before creating Win32 controls
Before creating Win32 controls, call InitCommonControlsEx() with INITCOMMONCONTROLSEX structure specifying ICC_STANDARD_CLASSES | ICC_WIN95_CLASSES to ensure controls are properly initialized.
Binding.gyp configuration for Windows C++ addon
The binding.gyp file must include: (1) conditions check for OS=="win" to compile only on Windows, (2) libraries field with Windows-specific libs like comctl32.lib and shcore.lib, (3) msvs_settings with VCCLCompilerTool and VCLinkerTool configurations, (4) defines for NODE_ADDON_API_CPP_EXCEPTIONS, WINVER=0x0A00, and _WIN32_WINNT=0x0A00.
VCCLCompilerTool settings explained
ExceptionHandling: 1 enables C++ exception handling with /EHsc flag, required for Node-API to handle exceptions between JavaScript and C++. DebugInformationFormat: "OldStyle" uses older PDB format for better compatibility. AdditionalOptions: ["/FS"] prevents parallel build errors by forcing serialized PDB file access.
VCLinkerTool GenerateDebugInformation setting
GenerateDebugInformation: "true" tells the linker to include debug symbols, enabling source-level debugging and human-readable stack traces if the addon crashes.
Windows version defines in binding.gyp
WINVER and _WIN32_WINNT should be set to the same value (e.g., 0x0A00 for Windows 10). This defines the minimum Windows version the code targets and allows use of features available in that version. Set to the lowest Windows version your Electron app needs to support.
Node-API threadsafe function for cross-thread callbacks
Use napi_create_threadsafe_function to create a threadsafe function that allows C++ code running on other threads to safely call JavaScript callbacks. The function signature is: napi_status napi_create_threadsafe_function(napi_env env, napi_value js_func, napi_value async_resource, napi_value async_resource_name, size_t max_queue_size, size_t initial_thread_count, void* thread_finalize_data, napi_finalize thread_finalize_cb, void* context, napi_threadsafe_function_call_js call_js_cb, napi_threadsafe_function* result).
Call threadsafe function from C++ thread
Use napi_call_threadsafe_function(tsfn, data, napi_tsfn_blocking) to invoke the threadsafe function from a native thread. The data pointer is passed to the callback function and must be cleaned up in the callback.
Release threadsafe function before addon destruction
Call napi_release_threadsafe_function(tsfn, napi_tsfn_release) in the addon destructor to properly clean up the threadsafe function. Store and initialize tsfn_ to nullptr in the constructor.
Must call destroy() on native addon before app quit
The JavaScript wrapper must call addon.destroy() before the app quits (in will-quit or before-quit event handler). Without this, persistent Napi references and the threadsafe function will prevent the addon destructor from running, causing Electron to hang on exit. The destroy() method should call callbacks.Reset(), emitter.Reset(), and napi_release_threadsafe_function with napi_tsfn_abort.
Convert Windows SYSTEMTIME to JavaScript milliseconds
To convert Windows SYSTEMTIME to JavaScript timestamp in milliseconds since epoch: call SystemTimeToFileTime to convert to FILETIME, extract low and high parts into ULARGE_INTEGER, then calculate (uli.QuadPart - 116444736000000000ULL) / 10000. This accounts for the Windows epoch difference (1601 vs 1970) and converts 100-nanosecond intervals to milliseconds.
Win32 window class registration
Register a window class with RegisterClassExW() passing a WNDCLASSEXW structure containing: cbSize = sizeof(WNDCLASSEXW), lpfnWndProc pointing to the window procedure function, hInstance from GetModuleHandle(nullptr), and lpszClassName as the class name.
Win32 window creation with DPI scaling
Create windows with CreateWindowExW() using scaled dimensions and positions calculated with the Scale() helper. Pass WS_OVERLAPPEDWINDOW as style, CW_USEDEFAULT for default positioning, and the scaled width/height values.
Win32 message loop structure
Implement the message loop with: MSG msg = {}; while (GetMessage(&msg, nullptr, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }. GetMessage returns 0 when WM_QUIT is received, ending the loop.
Create Win32 controls with CreateWindowExW
Win32 controls are created using CreateWindowExW with: control class name (e.g., WC_EDITW, WC_BUTTONW, WC_LISTBOXW, DATETIMEPICK_CLASSW), window styles (WS_CHILD, WS_VISIBLE, control-specific flags), scaled position and size, parent window handle, menu ID cast as HMENU, module handle, and nullptr.
Apply font to Win32 controls with WM_SETFONT
After creating a Win32 control, send WM_SETFONT message: SendMessageW(hControl, WM_SETFONT, (WPARAM)hFont, TRUE). Create fonts with CreateFontW() specifying DPI-scaled height (-Scale(14, dpi)), font name (e.g., L"Segoe UI"), and quality (CLEARTYPE_QUALITY).
Win32 window procedure handles messages
Implement LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) to handle window messages. Use switch(uMsg) to handle specific messages like WM_COMMAND for button clicks and WM_DESTROY. Use GetDlgItem(hwnd, controlId) to retrieve control handles by their menu ID. Call DefWindowProcW for unhandled messages.
Win32 control messages for data retrieval
Retrieve Win32 control data using: GetDlgItemTextW(hwnd, controlId, buffer, bufferSize) for edit box text, DateTime_GetSystemtime(hDatePicker, &st) for date picker value. Send messages to controls with SendMessageW(hControl, message, wParam, lParam).
Objective-C autorelease memory management with CLANG_ENABLE_OBJC_ARC
Set CLANG_ENABLE_OBJC_ARC to YES in xcode_settings to enable Automatic Reference Counting (ARC), which automatically manages memory for Objective-C objects. This simplifies memory management compared to manual retain/release.
Native macOS addon with Objective-C requires .mm file extension
Source files for Objective-C++ addons must use the .mm extension, which indicates that the file can mix Objective-C and C++ code. This is required when bridging between native Objective-C code and JavaScript through N-API.
binding.gyp configuration for macOS Objective-C addons
For macOS-specific Objective-C addons, binding.gyp must include: OS condition check for 'mac', source files with .mm extension, libraries section with '-framework Foundation' and '-framework AppKit', xcode_settings with CLANG_ENABLE_OBJC_ARC set to YES, OTHER_CFLAGS including '-ObjC++' and '-std=c++17', MACOSX_DEPLOYMENT_TARGET (e.g. '11.0'), and CLANG_CXX_LIBRARY set to 'libc++'.
All UI operations in macOS must run on the main thread
UI code in macOS/iOS applications must execute on the main thread where the application's event loop runs. When creating native windows or UI components from JavaScript callbacks that may be on worker threads, use Grand Central Dispatch (GCD) to dispatch to the main thread using dispatch_async(dispatch_get_main_queue(), ^{ ... }).
N-API threadsafe functions bridge thread boundaries for callbacks
Use napi_create_threadsafe_function to create a callback mechanism that safely bridges thread boundaries between native code (which may run on any thread) and JavaScript (which is single-threaded). This allows native UI events to safely invoke JavaScript callbacks without blocking or causing crashes.
JavaScript wrapper should extend EventEmitter for native events
Create a JavaScript wrapper class that extends EventEmitter to provide a clean API for native addon functionality. The wrapper should register event listeners with the native addon and forward events through the EventEmitter interface, converting native data formats (like JSON strings) to JavaScript objects.
Must call destroy() on native addon before app quit
Native Node.js addons must have their resources explicitly released by calling a destroy() method before the application quits. Without this, persistent references to callbacks and threadsafe functions will prevent the addon's destructor from running, causing Electron to hang on quit. Call this in the 'will-quit' or 'before-quit' event handler.
Platform check for macOS-only native modules
When distributing native addons that only work on macOS, check process.platform !== 'darwin' and throw an error or provide a fallback. This should be done both in the native module wrapper and in any code that imports it.
AppKit frameworks for macOS native UI development
AppKit is the primary UI framework for macOS applications. Foundation provides data management, file system interaction, and essential services. Both frameworks must be linked via the 'libraries' section of binding.gyp using '-framework Foundation' and '-framework AppKit'.
Cannot test native addons from Node.js directly, must use Electron
Native addons using macOS frameworks cannot be called from Node.js directly because Node.js does not set up an 'app' in the eyes of macOS. Test native addons by requiring and calling them from Electron, which properly sets up the application environment.
Static reference required to prevent native window controller deallocation
When creating native macOS windows from JavaScript, keep a static reference to the window controller to prevent it from being deallocated. Without this reference, the window will be destroyed when the C++ object is garbage collected.