diff --git a/doc/api/embedding.md b/doc/api/embedding.md index 114f1128af0a42..34382cb3bd22e3 100644 --- a/doc/api/embedding.md +++ b/doc/api/embedding.md @@ -167,8 +167,50 @@ 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_embedding_start` + + + +> Stability: 1 - Experimental + +Runs Node.js runtime instance the same way as the Node.js executable. + +```c +int32_t NAPI_CDECL node_embedding_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 [embedtest.cc]: https://github.com/nodejs/node/blob/HEAD/test/embedding/embedtest.cc +[node-api]: n-api.md [src/node.h]: https://github.com/nodejs/node/blob/HEAD/src/node.h diff --git a/node.gyp b/node.gyp index 83351d1d82627e..f71977c4e3e499 100644 --- a/node.gyp +++ b/node.gyp @@ -144,6 +144,7 @@ 'src/node_report.cc', 'src/node_report_module.cc', 'src/node_report_utils.cc', + 'src/node_runtime_api.cc', 'src/node_sea.cc', 'src/node_sea_bin.cc', 'src/node_serdes.cc', @@ -238,6 +239,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', @@ -282,6 +284,7 @@ 'src/node_report.h', 'src/node_revert.h', 'src/node_root_certs.h', + 'src/node_runtime_api.h', 'src/node_sea.h', 'src/node_shadow_realm.h', 'src/node_snapshotable.h', @@ -1389,6 +1392,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_runtime_api.cc b/src/node_runtime_api.cc new file mode 100644 index 00000000000000..bf40fb997dbbf8 --- /dev/null +++ b/src/node_runtime_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_runtime_api.h" +#include "node.h" + +EXTERN_C_START + +int32_t NAPI_CDECL node_embedding_start(int32_t argc, char* argv[]) { + return node::Start(argc, argv); +} + +EXTERN_C_END diff --git a/src/node_runtime_api.h b/src/node_runtime_api.h new file mode 100644 index 00000000000000..565c29ec82666e --- /dev/null +++ b/src/node_runtime_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_RUNTIME_API_H_ +#define SRC_NODE_RUNTIME_API_H_ + +#include "node_api.h" + +EXTERN_C_START + +// Runs Node.js main function. It is the same as running Node.js from CLI. +NAPI_EXTERN int32_t NAPI_CDECL node_embedding_start(int32_t argc, char* argv[]); + +EXTERN_C_END + +#endif // SRC_NODE_RUNTIME_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..03df188decece8 --- /dev/null +++ b/test/embedding/embedtest_c_api_main.c @@ -0,0 +1,8 @@ +#include "node_runtime_api.h" + +// The simplest Node.js embedding scenario where the Node.js main 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_embedding_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.js b/test/embedding/test-embedding.js index 79d79079b4b8c6..ce0f060491f078 100644 --- a/test/embedding/test-embedding.js +++ b/test/embedding/test-embedding.js @@ -162,3 +162,13 @@ if (!process.config.variables.node_without_node_options) { stderr: `${binary}: NODE_REPL_EXTERNAL_MODULE can't be used with kDisableNodeOptionsEnv${os.EOL}`, }); } + +// C-API specific tests +spawnSyncAndAssert( + binary, + ['c-api-nodejs-main', '--eval', 'console.log("Hello World")'], + { + trim: true, + stdout: 'Hello World', + }, +);