C++ API для встраивания
Node.js предоставляет ряд C++ API, которые можно использовать для выполнения JavaScript в среде Node.js из другого программного обеспечения на C++.
Документацию по этим API можно найти в src/node.h в исходном дереве Node.js. В дополнение к API, предоставляемым Node.js, некоторые необходимые концепции предоставляются API для встраивания V8.
Поскольку использование Node.js в качестве встроенной библиотеки отличается от написания кода, который выполняется Node.js, критические изменения не следуют типичной политике устаревания Node.js и могут происходить при каждом основном выпуске semver без предварительного уведомления.
Пример приложения для встраивания
В следующих разделах будет представлен обзор того, как использовать эти API для создания приложения с нуля, которое будет выполнять эквивалент node -e \<code\>
, т.е. принимать фрагмент JavaScript и запускать его в среде, специфичной для Node.js.
Полный код можно найти в исходном дереве Node.js.
Настройка состояния для каждого процесса
Для запуска Node.js требуется некоторое управление состоянием для каждого процесса:
- Разбор аргументов для параметров CLI Node.js,
- Требования V8 для каждого процесса, такие как экземпляр
v8::Platform
.
В следующем примере показано, как их можно настроить. Некоторые имена классов взяты из пространств имен node
и v8
C++ соответственно.
int main(int argc, char** argv) {
argv = uv_setup_args(argc, argv);
std::vector<std::string> args(argv, argv + argc);
// Разбор параметров CLI Node.js и вывод любых ошибок, возникших при
// попытке их разбора.
std::unique_ptr<node::InitializationResult> result =
node::InitializeOncePerProcess(args, {
node::ProcessInitializationFlags::kNoInitializeV8,
node::ProcessInitializationFlags::kNoInitializeNodeV8Platform
});
for (const std::string& error : result->errors())
fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str());
if (result->early_return() != 0) {
return result->exit_code();
}
// Создание экземпляра v8::Platform. `MultiIsolatePlatform::Create()` - это способ
// создать экземпляр v8::Platform, который Node.js может использовать при создании
// рабочих потоков. Когда нет экземпляра `MultiIsolatePlatform`,
// рабочие потоки отключаются.
std::unique_ptr<MultiIsolatePlatform> platform =
MultiIsolatePlatform::Create(4);
V8::InitializePlatform(platform.get());
V8::Initialize();
// Смотрите ниже содержимое этой функции.
int ret = RunNodeInstance(
platform.get(), result->args(), result->exec_args());
V8::Dispose();
V8::DisposePlatform();
node::TearDownOncePerProcess();
return ret;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Настройка состояния для каждого экземпляра
[История]
Версия | Изменения |
---|---|
v15.0.0 | Добавлены утилиты CommonEnvironmentSetup и SpinEventLoop . |
Node.js имеет концепцию "экземпляра Node.js", который обычно называют node::Environment
. Каждый node::Environment
связан со следующим:
- Ровно один
v8::Isolate
, то есть один экземпляр движка JS, - Ровно один
uv_loop_t
, то есть один цикл событий, - Несколько
v8::Context
ов, но ровно один основнойv8::Context
, и - Один экземпляр
node::IsolateData
, который содержит информацию, которая может совместно использоваться несколькимиnode::Environment
ами. Встраиватель должен убедиться, чтоnode::IsolateData
используется совместно только средиnode::Environment
ов, которые используют один и тот жеv8::Isolate
, Node.js не выполняет эту проверку.
Чтобы настроить v8::Isolate
, необходимо предоставить v8::ArrayBuffer::Allocator
. Одним из возможных вариантов является стандартный распределитель Node.js, который можно создать с помощью node::ArrayBufferAllocator::Create()
. Использование распределителя Node.js позволяет добиться небольших оптимизаций производительности, когда дополнения используют C++ API Buffer
Node.js, и требуется для отслеживания памяти ArrayBuffer
в process.memoryUsage()
.
Кроме того, каждый v8::Isolate
, который используется для экземпляра Node.js, должен быть зарегистрирован и отменен регистрацию в экземпляре MultiIsolatePlatform
, если он используется, чтобы платформа знала, какой цикл событий использовать для задач, запланированных v8::Isolate
.
Вспомогательная функция node::NewIsolate()
создает v8::Isolate
, настраивает его с помощью некоторых специфичных для Node.js перехватчиков (например, обработчика ошибок Node.js) и автоматически регистрирует его на платформе.
int RunNodeInstance(MultiIsolatePlatform* platform,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args) {
int exit_code = 0;
// Настройка цикла событий libuv, v8::Isolate и Node.js Environment.
std::vector<std::string> errors;
std::unique_ptr<CommonEnvironmentSetup> setup =
CommonEnvironmentSetup::Create(platform, &errors, args, exec_args);
if (!setup) {
for (const std::string& err : errors)
fprintf(stderr, "%s: %s\n", args[0].c_str(), err.c_str());
return 1;
}
Isolate* isolate = setup->isolate();
Environment* env = setup->env();
{
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
HandleScope handle_scope(isolate);
// v8::Context должен быть введен, когда вызываются node::CreateEnvironment() и
// node::LoadEnvironment().
Context::Scope context_scope(setup->context());
// Настройте экземпляр Node.js для выполнения и запустите код внутри него.
// Существует также вариант, который принимает обратный вызов и предоставляет ему
// объекты `require` и `process`, чтобы он мог вручную компилировать и
// запускать скрипты по мере необходимости.
// Функция `require` внутри этого скрипта не обращается к файловой системе и
// может загружать только встроенные модули Node.js.
// `module.createRequire()` используется для создания того, который может
// загружать файлы с диска и использует стандартный загрузчик файлов CommonJS
// вместо внутренней функции `require`.
MaybeLocal<Value> loadenv_ret = node::LoadEnvironment(
env,
"const publicRequire ="
" require('node:module').createRequire(process.cwd() + '/');"
"globalThis.require = publicRequire;"
"require('node:vm').runInThisContext(process.argv[1]);");
if (loadenv_ret.IsEmpty()) // Произошло исключение JS.
return 1;
exit_code = node::SpinEventLoop(env).FromMaybe(1);
// node::Stop() можно использовать для явной остановки цикла событий и
// предотвращения дальнейшего выполнения JavaScript. Его можно вызвать из
// любого потока и он будет действовать как worker.terminate(), если его
// вызвать из другого потока.
node::Stop(env);
}
return exit_code;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56