diff --git a/doc/api/embedding.md b/doc/api/embedding.md index 114f1128af0a42..452c4b3688ee93 100644 --- a/doc/api/embedding.md +++ b/doc/api/embedding.md @@ -167,6 +167,47 @@ int RunNodeInstance(MultiIsolatePlatform* platform, } ``` +## C runtime API + + + +While Node.js provides an extensive C++ embedding API that can be used from C++ +applications, the C-based API is useful when Node.js is embedded as a shared +libnode library into C++ or non-C++ applications. + +### API design overview + +One of the goals for the C based runtime API is to be ABI stable. It means that +applications must be able to use newer libnode versions without recompilation. +The following design principles are targeting to achieve that goal. + +* Follow the best practices for the [node-api][] design and build on top of + the [node-api][]. + +### API reference + +#### Functions + +##### `node_embed_start` + + + +> Stability: 1 - Experimental + +Runs Node.js runtime instance the same way as the Node.js executable. + +```c +int32_t NAPI_CDECL node_embed_start( + int32_t argc, + char* argv[]); +``` + +* `[in] argc`: Number of items in the `argv` array. +* `[in] argv`: CLI arguments as an array of zero terminated strings. + Returns `int32_t` with runtime instance exit code. + [CLI options]: cli.md [`process.memoryUsage()`]: process.md#processmemoryusage [deprecation policy]: deprecations.md diff --git a/node.gyp b/node.gyp index 2dd7eb1af5865a..2ae870295376c5 100644 --- a/node.gyp +++ b/node.gyp @@ -121,6 +121,7 @@ 'src/node_debug.cc', 'src/node_dir.cc', 'src/node_dotenv.cc', + 'src/node_embed_api.cc', 'src/node_env_var.cc', 'src/node_errors.cc', 'src/node_external_reference.cc', @@ -241,6 +242,7 @@ 'src/module_wrap.h', 'src/node.h', 'src/node_api.h', + 'src/node_api_internals.h', 'src/node_api_types.h', 'src/node_binding.h', 'src/node_blob.h', @@ -253,6 +255,7 @@ 'src/node_debug.h', 'src/node_dir.h', 'src/node_dotenv.h', + 'src/node_embed_api.h', 'src/node_errors.h', 'src/node_exit_code.h', 'src/node_external_reference.h', @@ -1410,6 +1413,8 @@ 'sources': [ 'src/node_snapshot_stub.cc', 'test/embedding/embedtest.cc', + 'test/embedding/embedtest_c_api_main.c', + 'test/embedding/embedtest_main.cc', ], 'conditions': [ diff --git a/src/node_embed_api.cc b/src/node_embed_api.cc new file mode 100644 index 00000000000000..c0359981c4a82c --- /dev/null +++ b/src/node_embed_api.cc @@ -0,0 +1,25 @@ +// +// Description: C-based API for embedding Node.js. +// +// !!! WARNING !!! WARNING !!! WARNING !!! +// This is a new API and is subject to change. +// While it is C-based, it is not ABI safe yet. +// Consider all functions and data structures as experimental. +// !!! WARNING !!! WARNING !!! WARNING !!! +// +// This file contains the C-based API for embedding Node.js in a host +// application. The API is designed to be used by applications that want to +// embed Node.js as a shared library (.so or .dll) and can interop with +// C-based API. +// + +#include "node_embed_api.h" +#include "node.h" + +EXTERN_C_START + +int32_t NAPI_CDECL node_embed_start(int32_t argc, char* argv[]) { + return node::Start(argc, argv); +} + +EXTERN_C_END diff --git a/src/node_embed_api.h b/src/node_embed_api.h new file mode 100644 index 00000000000000..ef2b4586315dc9 --- /dev/null +++ b/src/node_embed_api.h @@ -0,0 +1,28 @@ +// +// Description: C-based API for embedding Node.js. +// +// !!! WARNING !!! WARNING !!! WARNING !!! +// This is a new API and is subject to change. +// While it is C-based, it is not ABI safe yet. +// Consider all functions and data structures as experimental. +// !!! WARNING !!! WARNING !!! WARNING !!! +// +// This file contains the C-based API for embedding Node.js in a host +// application. The API is designed to be used by applications that want to +// embed Node.js as a shared library (.so or .dll) and can interop with +// C-based API. +// + +#ifndef SRC_NODE_EMBED_API_H_ +#define SRC_NODE_EMBED_API_H_ + +#include "node_api.h" + +EXTERN_C_START + +// Runs Node.js start function. It is the same as running Node.js from CLI. +NAPI_EXTERN int32_t NAPI_CDECL node_embed_start(int32_t argc, char* argv[]); + +EXTERN_C_END + +#endif // SRC_NODE_EMBED_API_H_ diff --git a/test/embedding/embedtest.cc b/test/embedding/embedtest.cc index 982eed74f9540b..b2b4b3533cb9c5 100644 --- a/test/embedding/embedtest.cc +++ b/test/embedding/embedtest.cc @@ -3,8 +3,8 @@ #endif #include #include "cppgc/platform.h" -#include "executable_wrapper.h" #include "node.h" +#include "uv.h" #include @@ -28,10 +28,7 @@ static int RunNodeInstance(MultiIsolatePlatform* platform, const std::vector& args, const std::vector& exec_args); -NODE_MAIN(int argc, node::argv_type raw_argv[]) { - char** argv = nullptr; - node::FixupMain(argc, raw_argv, &argv); - +int32_t test_main_cpp_api(int32_t argc, char* argv[]) { std::vector args(argv, argv + argc); std::shared_ptr result = node::InitializeOncePerProcess( diff --git a/test/embedding/embedtest_c_api_main.c b/test/embedding/embedtest_c_api_main.c new file mode 100644 index 00000000000000..5bc750fd70c356 --- /dev/null +++ b/test/embedding/embedtest_c_api_main.c @@ -0,0 +1,8 @@ +#include "node_embed_api.h" + +// The simplest Node.js embedding scenario where the Node.js start function is +// invoked from the libnode shared library as it would be run from the Node.js +// CLI. +int32_t test_main_c_api_nodejs_main(int32_t argc, char* argv[]) { + return node_embed_start(argc, argv); +} diff --git a/test/embedding/embedtest_main.cc b/test/embedding/embedtest_main.cc new file mode 100644 index 00000000000000..323505532f91a1 --- /dev/null +++ b/test/embedding/embedtest_main.cc @@ -0,0 +1,37 @@ +#include +#include +#include +#include "executable_wrapper.h" + +int32_t test_main_cpp_api(int32_t argc, char* argv[]); + +extern "C" int32_t test_main_c_api_nodejs_main(int32_t argc, char* argv[]); + +using MainCallback = int32_t (*)(int32_t argc, char* argv[]); + +int32_t CallWithoutArg1(MainCallback main, int32_t argc, char* argv[]) { + for (int32_t i = 2; i < argc; i++) { + argv[i - 1] = argv[i]; + } + argv[--argc] = nullptr; + return main(argc, argv); +} + +NODE_MAIN(int32_t argc, node::argv_type raw_argv[]) { + char** argv = nullptr; + node::FixupMain(argc, raw_argv, &argv); + + const std::unordered_map main_map = { + {"cpp-api", test_main_cpp_api}, + {"c-api-nodejs-main", test_main_c_api_nodejs_main}, + }; + if (argc > 1) { + char* arg1 = argv[1]; + for (const auto& [key, value] : main_map) { + if (key == arg1) { + return CallWithoutArg1(value, argc, argv); + } + } + } + return test_main_cpp_api(argc, argv); +} diff --git a/test/embedding/test-embedding-c-api-start.js b/test/embedding/test-embedding-c-api-start.js new file mode 100644 index 00000000000000..a7ea00ca76c18f --- /dev/null +++ b/test/embedding/test-embedding-c-api-start.js @@ -0,0 +1,15 @@ +'use strict'; + +// Tests that we can run the C-API node_embed_start. + +const common = require('../common'); +const { spawnSyncAndAssert } = require('../common/child_process'); + +spawnSyncAndAssert( + common.resolveBuiltBinary('embedtest'), + ['c-api-nodejs-main', '--eval', 'console.log("Hello World")'], + { + trim: true, + stdout: 'Hello World', + }, +);