From 87f957d714e77b814a8ba962f18d04dd1f7d1b45 Mon Sep 17 00:00:00 2001 From: hexbinoct Date: Thu, 13 Aug 2026 13:15:59 +0500 Subject: [PATCH] build: delay-load node.exe imports on Windows The MSVC-built addons bind their napi_* imports to a module literally named NODE.EXE, so only a host process named node.exe can load them. Delay-load those imports and resolve them to the current process image with a delay-load hook, the same approach node-gyp uses, so any Node-API host executable can load the addons regardless of its name. Signed-off-by: hexbinoct --- CMakeLists.txt | 8 ++++++++ src/win_delay_load_hook.cc | 39 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 src/win_delay_load_hook.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index cf67540..286ab29 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,14 @@ function(add_node_api_cts_addon ADDON_NAME) endif() target_include_directories(${ADDON_NAME} PRIVATE ${NODE_API_HEADERS_DIR}) target_link_libraries(${ADDON_NAME} PRIVATE ${NODE_API_LIB}) + if(MSVC) + # Delay-load the node.exe imports and resolve them against the host + # process at runtime, so the addons can be loaded by any Node-API host + # executable regardless of its file name (the node-gyp approach). + target_sources(${ADDON_NAME} PRIVATE ${PROJECT_SOURCE_DIR}/src/win_delay_load_hook.cc) + target_link_libraries(${ADDON_NAME} PRIVATE delayimp) + target_link_options(${ADDON_NAME} PRIVATE "/DELAYLOAD:NODE.EXE") + endif() target_compile_features(${ADDON_NAME} PRIVATE cxx_std_17) target_compile_definitions(${ADDON_NAME} PRIVATE ADDON_NAME=${ADDON_NAME}) endfunction() diff --git a/src/win_delay_load_hook.cc b/src/win_delay_load_hook.cc new file mode 100644 index 0000000..65a0557 --- /dev/null +++ b/src/win_delay_load_hook.cc @@ -0,0 +1,39 @@ +/* + * When this file is linked to a DLL, it sets up a delay-load hook that + * intervenes when the DLL is trying to load 'node.exe' dynamically. Instead + * of trying to locate the .exe file it'll just return a handle to the + * process image. + * + * This allows the test addons to load into any Node-API host executable, + * regardless of its file name. Same approach as node-gyp's + * win_delay_load_hook.cc. + */ + +#ifdef _MSC_VER + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include + +#include +#include + +static FARPROC WINAPI load_exe_hook(unsigned int event, DelayLoadInfo* info) { + HMODULE m; + if (event != dliNotePreLoadLibrary) + return NULL; + + if (_stricmp(info->szDll, "node.exe") != 0) + return NULL; + + // Prefer libnode.dll to support a Node.js built as a shared library. + m = GetModuleHandle(TEXT("libnode.dll")); + if (m == NULL) m = GetModuleHandle(NULL); + return (FARPROC) m; +} + +decltype(__pfnDliNotifyHook2) __pfnDliNotifyHook2 = load_exe_hook; + +#endif