-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
node-api,src: fix module registration in MSVC C++ #42459
Conversation
Review requested:
|
src/node.h
Outdated
#else | ||
# define NODE_CTOR_PREFIX static | ||
# define NODE_CTOR_NAMESPACE namespace |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to avoid the lint-cpp issue with using anonymous namespace {
in the header files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
@mhdawson, the build fails because the new code uses the new C++17 features and it is being compiled by VS2015.
I am going to change the PR to enable the fix only for MSVC versions that support C++17 and keep the old behavior for older MSVC versions. |
src/node_api.h
Outdated
namespace { \ | ||
struct fn##_ { \ | ||
static int Call##fn() { return (fn(), 0); } \ | ||
static inline const int x = Call##fn(); \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it needed to implement this by using a static inline
which requires C++ 17?
The variant there requires no new C++ but it still avoids the need for the pragma
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Flarna, no, we do not need it in this context. It is only required for the changes in the node.h to support the shared mode. Since the support for VS2015 is required for addons, I am going to refactor the code in a way that only that shared mode is going to require the C++17 and all other places are going to use the method you proposed above,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it needed in node.h but not here? What's the difference between using napi or node in this regard?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
node.h has macro NODE_SHARED_MODE
that removes the static
prefix for the registration function.
I assume this is done to support using the NODE_C_CTOR
macro in header files.
With functions that approach works, but initialization of static class variables which we want to use in MSVC C++ it does not work. Thus, I decided to use the new C++17 inline static
that was created for such scenarios. My hope was that C++17 is the requirement that we have for Node.js code and its modules.
I am going to research it a little bit more: I believe the C++11 static field initialization in headers can be addressed by using templates. If not, then I will keep use of C++17 inline static
only for NODE_SHARED_MODE
and use simple static field initialization for other scenarios.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems that the shared mode introduced in 410296c was required for compiling Node.js as a shared module. It has nothing to do with being able to use registration in header files. Thus, no complexity is needed.
There is a doc that has some info about addons/windows compilers but I can't seem to find it. @joaocgreis, @richardlau do you know where it is? |
https://github.com/nodejs/build/blob/master/doc/windows-visualstudio-supported-versions.md? Although it hasn't been updated since Node.js 15. |
Based on the doc, we must support VS 2015 for addon developers. |
@mhdawson, I am going to have a new iteration for this PR that uses the C++17 inline variables only for shared |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Marking as request changes so we don't land until updates as mentioned by @vmoroz
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
Seems like the added test requires an ifdef C++17 as it uses
|
You are right. My intention was to have a test that matches the scenario described in the issue #41852. |
PR-URL: nodejs#42459 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de>
This is causing this error in the v16.x branch: make[1]: Leaving directory '/home/juanarbol/GitHub/node/test/node-api/test_make_callback/build'
Building addon in /home/juanarbol/GitHub/node/test/node-api/test_policy
/home/juanarbol/GitHub/node/tools/build-addons.js:58
main(process.argv[3]).catch((err) => setImmediate(() => { throw err; }));
^
Error: Command failed: /home/juanarbol/GitHub/node/out/Release/node /home/juanarbol/GitHub/node/deps/npm/node_mo
dules/node-gyp/bin/node-gyp.js rebuild --directory=/home/juanarbol/GitHub/node/test/node-api/test_init_order
../test_init_order.cc:46:40: error: no member named 'size' in namespace 'std'
env, exports, std::size(descriptors), descriptors));
~~~~~^
../../../js-native-api/common.h:53:27: note: expanded from macro 'NODE_API_CALL'
NODE_API_CALL_BASE(env, the_call, NULL)
^~~~~~~~
../../../js-native-api/common.h:45:10: note: expanded from macro 'NODE_API_CALL_BASE'
if ((the_call) != napi_ok) { \
^~~~~~~~
1 error generated.
make[1]: *** [test_init_order.target.mk:111: Release/obj.target/test_init_order/test_init_order.o] Error 1
at ChildProcess.exithandler (node:child_process:398:12)
at ChildProcess.emit (node:events:527:28)
at maybeClose (node:internal/child_process:1092:16)
at Process.ChildProcess._handle.onexit (node:internal/child_process:302:5) {
code: 1,
killed: false,
signal: null,
cmd: '/home/juanarbol/GitHub/node/out/Release/node /home/juanarbol/GitHub/node/deps/npm/node_modules/node-gyp/
bin/node-gyp.js rebuild --directory=/home/juanarbol/GitHub/node/test/node-api/test_init_order',
stdout: "make[1]: Entering directory '/home/juanarbol/GitHub/node/test/node-api/test_init_order/build'\n" +
' CXX(target) Release/obj.target/test_init_order/test_init_order.o\n' +
"make[1]: Leaving directory '/home/juanarbol/GitHub/node/test/node-api/test_init_order/build'\n",
stderr: "../test_init_order.cc:46:40: error: no member named 'size' in namespace 'std'\n" +
' env, exports, std::size(descriptors), descriptors));\n' +
' ~~~~~^\n' +
"../../../js-native-api/common.h:53:27: note: expanded from macro 'NODE_API_CALL'\n" +
' NODE_API_CALL_BASE(env, the_call, NULL)\n' +
' ^~~~~~~~\n' +
"../../../js-native-api/common.h:45:10: note: expanded from macro 'NODE_API_CALL_BASE'\n" +
' if ((the_call) != napi_ok) { \\\n' +
' ^~~~~~~~\n' +
'1 error generated.\n' +
'make[1]: *** [test_init_order.target.mk:111: Release/obj.target/test_init_order/test_init_order.o] Error 1\
n'
}
make: *** [Makefile:437: test/node-api/.buildstamp] Error 1 |
@juanarbol , @mhdawson , how can I address this issue? Should I create a new PR, or somehow augment this PR? |
@vmoroz You can check out the backport docs for detailed steps: https://github.com/nodejs/node/blob/master/doc/contributing/backporting-to-release-lines.md. I believe we have an |
A similar backport PR would be needed for v14 (after it landed on v16 and a v16 release including it is available for a while). |
@Flarna , sorry, I do not have permissions to add/change any labels. |
I added the |
PR-URL: nodejs#42459 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de>
PR-URL: nodejs#42459 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de> Backport-PR-URL: nodejs#43293
Thank you, @legendecas , for the reference: I followed it the best way I could. |
As a side note: I was surprised that I could not compile Node v16.x in Visual Studio 2022. |
PR-URL: nodejs/node#42459 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Michael Dawson <midawson@redhat.com> Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de> Backport-PR-URL: nodejs/node#43293
This PR addresses the issue #41852 where the use of the
.CRT$XCU
section byNAPI_C_CTOR
andNODE_C_CTOR
macros can prevent dynamic initialization of C++ static variables by MSVC.Issue details
We use the
NAPI_C_CTOR
andNODE_C_CTOR
macros to register Node-API and legacy Node modules.They make sure that the registration function is called when an addon shared library (
.dll
or.so
) is loaded.Typically, developers use C++ static variable dynamic initializers (when a variable is initialized by a function call) to call such registration functions. The standard C programming language does not have such facility and the C-based code must rely on the compiler-specific extensions to achieve the same results:
__attribute__((constructor))
to specify a function that must be called on shared library load..CRT$XCU
section.We use both these techniques in
NAPI_C_CTOR
andNODE_C_CTOR
macros depending on the compiler.But when these macros are used in C++ code as we see in #41852, it can interfere with the MSVC C++ static variable initialization.
Solution
The solution for this issue is to use different code for C++ vs C in
node_api.h
.In
node.h
we can just switch to C++ code because this header can be used only by C++ code.This PR does the change only for MSVC, but we can adopt the same technique for other compilers in future.
We call the registration function as a part of the C++ static variable initialization.
Note that we use an anonymous namespace to match the
static
keyword used for the functions.Resolve #41852