diff --git a/src/node.h b/src/node.h index b96b3ea1e49d4a..dec8dd6805fac9 100644 --- a/src/node.h +++ b/src/node.h @@ -827,11 +827,13 @@ extern "C" NODE_EXTERN void node_module_register(void* mod); #endif #if defined(_MSC_VER) -#pragma section(".CRT$XCU", read) #define NODE_C_CTOR(fn) \ NODE_CTOR_PREFIX void __cdecl fn(void); \ - __declspec(dllexport, allocate(".CRT$XCU")) \ - void (__cdecl*fn ## _)(void) = fn; \ + namespace { \ + struct fn##_ { \ + fn##_() { fn(); }; \ + } fn##_v_; \ + } \ NODE_CTOR_PREFIX void __cdecl fn(void) #else #define NODE_C_CTOR(fn) \ diff --git a/src/node_api.h b/src/node_api.h index e8e903b62a893b..d95046676a699d 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -44,12 +44,29 @@ typedef struct napi_module { #define NAPI_MODULE_VERSION 1 #if defined(_MSC_VER) +#if defined(__cplusplus) +#define NAPI_C_CTOR(fn) \ + static void __cdecl fn(void); \ + namespace { \ + struct fn##_ { \ + fn##_() { fn(); } \ + } fn##_v_; \ + } \ + static void __cdecl fn(void) +#else // !defined(__cplusplus) #pragma section(".CRT$XCU", read) +// The NAPI_C_CTOR macro defines a function fn that is called during CRT +// initialization. +// C does not support dynamic initialization of static variables and this code +// simulates C++ behavior. Exporting the function pointer prevents it from being +// optimized. See for details: +// https://docs.microsoft.com/en-us/cpp/c-runtime-library/crt-initialization?view=msvc-170 #define NAPI_C_CTOR(fn) \ static void __cdecl fn(void); \ __declspec(dllexport, allocate(".CRT$XCU")) void(__cdecl * fn##_)(void) = \ fn; \ static void __cdecl fn(void) +#endif // defined(__cplusplus) #else #define NAPI_C_CTOR(fn) \ static void fn(void) __attribute__((constructor)); \ diff --git a/test/js-native-api/common.h b/test/js-native-api/common.h index 73f60906630140..46784059a1f70a 100644 --- a/test/js-native-api/common.h +++ b/test/js-native-api/common.h @@ -62,6 +62,9 @@ #define DECLARE_NODE_API_GETTER(name, func) \ { (name), NULL, NULL, (func), NULL, NULL, napi_default, NULL } +#define DECLARE_NODE_API_PROPERTY_VALUE(name, value) \ + { (name), NULL, NULL, NULL, NULL, (value), napi_default, NULL } + void add_returned_status(napi_env env, const char* key, napi_value object, diff --git a/test/node-api/test_init_order/binding.gyp b/test/node-api/test_init_order/binding.gyp new file mode 100644 index 00000000000000..2ffb81838d1189 --- /dev/null +++ b/test/node-api/test_init_order/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_init_order", + "sources": [ "test_init_order.cc" ] + } + ] +} diff --git a/test/node-api/test_init_order/test.js b/test/node-api/test_init_order/test.js new file mode 100644 index 00000000000000..0bfeb8f94afebb --- /dev/null +++ b/test/node-api/test_init_order/test.js @@ -0,0 +1,10 @@ +'use strict'; + +// This test verifies that C++ static variable dynamic initialization is called +// correctly and does not interfere with the module initialization. +const common = require('../../common'); +const test_init_order = require(`./build/${common.buildType}/test_init_order`); +const assert = require('assert'); + +assert.strictEqual(test_init_order.cppIntValue, 42); +assert.strictEqual(test_init_order.cppStringValue, '123'); diff --git a/test/node-api/test_init_order/test_init_order.cc b/test/node-api/test_init_order/test_init_order.cc new file mode 100644 index 00000000000000..fc4174c09d8dba --- /dev/null +++ b/test/node-api/test_init_order/test_init_order.cc @@ -0,0 +1,52 @@ +#include +#include +#include +#include "../../js-native-api/common.h" + +// This test verifies that use of the NAPI_MODULE in C++ code does not +// interfere with the C++ dynamic static initializers. + +namespace { + +// This class uses dynamic static initializers for the test. +// In production code developers must avoid dynamic static initializers because +// they affect the start up time. They must prefer static initialization such as +// use of constexpr functions or classes with constexpr constructors. E.g. +// instead of using std::string, it is preferrable to use const char[], or +// constexpr std::string_view starting with C++17, or even constexpr +// std::string starting with C++20. +struct MyClass { + static const std::unique_ptr valueHolder; + static const std::string testString; +}; + +const std::unique_ptr MyClass::valueHolder = + std::unique_ptr(new int(42)); +// NOLINTNEXTLINE(runtime/string) +const std::string MyClass::testString = std::string("123"); + +} // namespace + +EXTERN_C_START +napi_value Init(napi_env env, napi_value exports) { + napi_value cppIntValue, cppStringValue; + NODE_API_CALL(env, + napi_create_int32(env, *MyClass::valueHolder, &cppIntValue)); + NODE_API_CALL( + env, + napi_create_string_utf8( + env, MyClass::testString.c_str(), NAPI_AUTO_LENGTH, &cppStringValue)); + + napi_property_descriptor descriptors[] = { + DECLARE_NODE_API_PROPERTY_VALUE("cppIntValue", cppIntValue), + DECLARE_NODE_API_PROPERTY_VALUE("cppStringValue", cppStringValue)}; + + NODE_API_CALL(env, + napi_define_properties( + env, exports, std::size(descriptors), descriptors)); + + return exports; +} +EXTERN_C_END + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)