Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v16.x backport] node-api,src: fix module registration in MSVC C++ #43293

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -824,11 +824,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) \
Expand Down
17 changes: 17 additions & 0 deletions src/node_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)); \
Expand Down
3 changes: 3 additions & 0 deletions test/js-native-api/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 8 additions & 0 deletions test/node-api/test_init_order/binding.gyp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"targets": [
{
"target_name": "test_init_order",
"sources": [ "test_init_order.cc" ]
}
]
}
10 changes: 10 additions & 0 deletions test/node-api/test_init_order/test.js
Original file line number Diff line number Diff line change
@@ -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');
55 changes: 55 additions & 0 deletions test/node-api/test_init_order/test_init_order.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include <node_api.h>
#include <memory>
#include <string>
#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<int> valueHolder;
static const std::string testString;
};

const std::unique_ptr<int> MyClass::valueHolder =
std::unique_ptr<int>(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,
sizeof(descriptors) / sizeof(descriptors[0]),
descriptors));

return exports;
}
EXTERN_C_END

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)