Skip to content

Commit c7cc5cc

Browse files
committed
node-api: minimal C node_embedding_api function
Co-authored-by: vmoroz <vmorozov@microsoft.com>
1 parent 8edeff9 commit c7cc5cc

File tree

8 files changed

+164
-12
lines changed

8 files changed

+164
-12
lines changed

doc/api/embedding.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,47 @@ int RunNodeInstance(MultiIsolatePlatform* platform,
167167
}
168168
```
169169

170+
## C runtime API
171+
172+
<!--introduced_in=REPLACEME-->
173+
174+
While Node.js provides an extensive C++ embedding API that can be used from C++
175+
applications, the C-based API is useful when Node.js is embedded as a shared
176+
libnode library into C++ or non-C++ applications.
177+
178+
### API design overview
179+
180+
One of the goals for the C based runtime API is to be ABI stable. It means that
181+
applications must be able to use newer libnode versions without recompilation.
182+
The following design principles are targeting to achieve that goal.
183+
184+
* Follow the best practices for the [node-api][] design and build on top of
185+
the [node-api][].
186+
187+
### API reference
188+
189+
#### Functions
190+
191+
##### `node_embed_start`
192+
193+
<!-- YAML
194+
added: REPLACEME
195+
-->
196+
197+
> Stability: 1 - Experimental
198+
199+
Runs Node.js runtime instance the same way as the Node.js executable.
200+
201+
```c
202+
int32_t NAPI_CDECL node_embed_start(
203+
int32_t argc,
204+
char* argv[]);
205+
```
206+
207+
* `[in] argc`: Number of items in the `argv` array.
208+
* `[in] argv`: CLI arguments as an array of zero terminated strings.
209+
Returns `int32_t` with runtime instance exit code.
210+
170211
[CLI options]: cli.md
171212
[`process.memoryUsage()`]: process.md#processmemoryusage
172213
[deprecation policy]: deprecations.md

node.gyp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@
121121
'src/node_debug.cc',
122122
'src/node_dir.cc',
123123
'src/node_dotenv.cc',
124+
'src/node_embed_api.cc',
124125
'src/node_env_var.cc',
125126
'src/node_errors.cc',
126127
'src/node_external_reference.cc',
@@ -241,6 +242,7 @@
241242
'src/module_wrap.h',
242243
'src/node.h',
243244
'src/node_api.h',
245+
'src/node_api_internals.h',
244246
'src/node_api_types.h',
245247
'src/node_binding.h',
246248
'src/node_blob.h',
@@ -253,6 +255,7 @@
253255
'src/node_debug.h',
254256
'src/node_dir.h',
255257
'src/node_dotenv.h',
258+
'src/node_embed_api.h',
256259
'src/node_errors.h',
257260
'src/node_exit_code.h',
258261
'src/node_external_reference.h',
@@ -895,6 +898,7 @@
895898
'<(SHARED_INTERMEDIATE_DIR)' # for node_natives.h
896899
],
897900
'dependencies': [
901+
'tools/v8_gypfiles/abseil.gyp:abseil',
898902
'node_js2c#host',
899903
],
900904

@@ -955,9 +959,6 @@
955959
'src/node_snapshot_stub.cc',
956960
]
957961
}],
958-
[ 'node_use_bundled_v8!="false"', {
959-
'dependencies': [ 'tools/v8_gypfiles/abseil.gyp:abseil' ],
960-
}],
961962
[ 'node_shared_gtest=="false"', {
962963
'dependencies': [
963964
'deps/googletest/googletest.gyp:gtest_prod',
@@ -1278,6 +1279,7 @@
12781279

12791280
'dependencies': [
12801281
'<(node_lib_target_name)',
1282+
'tools/v8_gypfiles/abseil.gyp:abseil',
12811283
],
12821284

12831285
'includes': [
@@ -1311,9 +1313,6 @@
13111313
[ 'node_shared_gtest=="true"', {
13121314
'libraries': [ '-lgtest_main' ],
13131315
}],
1314-
[ 'node_use_bundled_v8!="false"', {
1315-
'dependencies': [ 'tools/v8_gypfiles/abseil.gyp:abseil' ],
1316-
}],
13171316
[ 'node_shared_hdr_histogram=="false"', {
13181317
'dependencies': [
13191318
'deps/histogram/histogram.gyp:histogram',
@@ -1410,6 +1409,8 @@
14101409
'sources': [
14111410
'src/node_snapshot_stub.cc',
14121411
'test/embedding/embedtest.cc',
1412+
'test/embedding/embedtest_c_api_main.c',
1413+
'test/embedding/embedtest_main.cc',
14131414
],
14141415

14151416
'conditions': [
@@ -1552,7 +1553,7 @@
15521553
[ 'OS=="mac"', {
15531554
'libraries': [ '-framework CoreFoundation -framework Security' ],
15541555
}],
1555-
[ 'node_shared_simdutf=="false" and node_use_bundled_v8!="false"', {
1556+
[ 'node_shared_simdutf=="false"', {
15561557
'dependencies': [ 'tools/v8_gypfiles/v8.gyp:simdutf#host' ],
15571558
}],
15581559
[ 'node_shared_libuv=="false"', {

src/node_embed_api.cc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//
2+
// Description: C-based API for embedding Node.js.
3+
//
4+
// !!! WARNING !!! WARNING !!! WARNING !!!
5+
// This is a new API and is subject to change.
6+
// While it is C-based, it is not ABI safe yet.
7+
// Consider all functions and data structures as experimental.
8+
// !!! WARNING !!! WARNING !!! WARNING !!!
9+
//
10+
// This file contains the C-based API for embedding Node.js in a host
11+
// application. The API is designed to be used by applications that want to
12+
// embed Node.js as a shared library (.so or .dll) and can interop with
13+
// C-based API.
14+
//
15+
16+
#include "node_embed_api.h"
17+
#include "node.h"
18+
19+
EXTERN_C_START
20+
21+
int32_t NAPI_CDECL node_embed_start(int32_t argc, char* argv[]) {
22+
return node::Start(argc, argv);
23+
}
24+
25+
EXTERN_C_END

src/node_embed_api.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// Description: C-based API for embedding Node.js.
3+
//
4+
// !!! WARNING !!! WARNING !!! WARNING !!!
5+
// This is a new API and is subject to change.
6+
// While it is C-based, it is not ABI safe yet.
7+
// Consider all functions and data structures as experimental.
8+
// !!! WARNING !!! WARNING !!! WARNING !!!
9+
//
10+
// This file contains the C-based API for embedding Node.js in a host
11+
// application. The API is designed to be used by applications that want to
12+
// embed Node.js as a shared library (.so or .dll) and can interop with
13+
// C-based API.
14+
//
15+
16+
#ifndef SRC_NODE_EMBED_API_H_
17+
#define SRC_NODE_EMBED_API_H_
18+
19+
#include "node_api.h"
20+
21+
EXTERN_C_START
22+
23+
// Runs Node.js start function. It is the same as running Node.js from CLI.
24+
NAPI_EXTERN int32_t NAPI_CDECL node_embed_start(int32_t argc, char* argv[]);
25+
26+
EXTERN_C_END
27+
28+
#endif // SRC_NODE_EMBED_API_H_

test/embedding/embedtest.cc

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
#endif
44
#include <assert.h>
55
#include "cppgc/platform.h"
6-
#include "executable_wrapper.h"
76
#include "node.h"
7+
#include "uv.h"
88

99
#include <algorithm>
1010

@@ -28,10 +28,7 @@ static int RunNodeInstance(MultiIsolatePlatform* platform,
2828
const std::vector<std::string>& args,
2929
const std::vector<std::string>& exec_args);
3030

31-
NODE_MAIN(int argc, node::argv_type raw_argv[]) {
32-
char** argv = nullptr;
33-
node::FixupMain(argc, raw_argv, &argv);
34-
31+
int32_t test_main_cpp_api(int32_t argc, char* argv[]) {
3532
std::vector<std::string> args(argv, argv + argc);
3633
std::shared_ptr<node::InitializationResult> result =
3734
node::InitializeOncePerProcess(
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include "node_embed_api.h"
2+
3+
// The simplest Node.js embedding scenario where the Node.js start function is
4+
// invoked from the libnode shared library as it would be run from the Node.js
5+
// CLI.
6+
int32_t test_main_c_api_nodejs_main(int32_t argc, char* argv[]) {
7+
return node_embed_start(argc, argv);
8+
}

test/embedding/embedtest_main.cc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#include <cstring>
2+
#include <string_view>
3+
#include <unordered_map>
4+
#include "executable_wrapper.h"
5+
6+
int32_t test_main_cpp_api(int32_t argc, char* argv[]);
7+
8+
extern "C" int32_t test_main_c_api_nodejs_main(int32_t argc, char* argv[]);
9+
10+
using MainCallback = int32_t (*)(int32_t argc, char* argv[]);
11+
12+
int32_t CallWithoutArg1(MainCallback main, int32_t argc, char* argv[]) {
13+
for (int32_t i = 2; i < argc; i++) {
14+
argv[i - 1] = argv[i];
15+
}
16+
argv[--argc] = nullptr;
17+
return main(argc, argv);
18+
}
19+
20+
NODE_MAIN(int32_t argc, node::argv_type raw_argv[]) {
21+
char** argv = nullptr;
22+
node::FixupMain(argc, raw_argv, &argv);
23+
24+
const std::unordered_map<std::string_view, MainCallback> main_map = {
25+
{"cpp-api", test_main_cpp_api},
26+
{"c-api-nodejs-main", test_main_c_api_nodejs_main},
27+
};
28+
if (argc > 1) {
29+
char* arg1 = argv[1];
30+
for (const auto& [key, value] : main_map) {
31+
if (key == arg1) {
32+
return CallWithoutArg1(value, argc, argv);
33+
}
34+
}
35+
}
36+
return test_main_cpp_api(argc, argv);
37+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict';
2+
3+
// Tests that we can run the C-API node_embed_start.
4+
5+
const common = require('../common');
6+
const { spawnSyncAndAssert } = require('../common/child_process');
7+
8+
spawnSyncAndAssert(
9+
common.resolveBuiltBinary('embedtest'),
10+
['c-api-nodejs-main', '--eval', 'console.log("Hello World")'],
11+
{
12+
trim: true,
13+
stdout: 'Hello World',
14+
},
15+
);

0 commit comments

Comments
 (0)