C++ Embedder API
Node.js предоставляет ряд C++ API, которые можно использовать для выполнения JavaScript в среде Node.js из другого C++ программного обеспечения.
Документация по этим API находится в src/node.h в дереве исходных кодов Node.js. В дополнение к API, предоставляемым Node.js, некоторые необходимые концепции предоставляются API V8 embedder.
Поскольку использование Node.js как встроенной библиотеки отличается от написания кода, выполняемого Node.js, изменения, нарушающие работу, не следуют типичной политике прекращения поддержки Node.js deprecation policy и могут происходить при каждом выпуске semver-major без предварительного предупреждения.
Пример приложения встраивания
Следующие разделы предоставят обзор того, как использовать эти API для создания приложения с нуля, которое выполнит эквивалент node -e <code>, т. е. которое примет фрагмент JavaScript и запустит его в среде, специфичной для Node.js.
Полный код можно найти в дереве исходных кодов Node.js.
Настройка состояния на процесс
Node.js требует некоторого управления состоянием на процесс для выполнения:
- Разбора аргументов для опций командной строки Node.js CLI options,
- Требований 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);
std::vector<std::string> exec_args;
std::vector<std::string> errors;
// Parse Node.js CLI options, and print any errors that have occurred while
// trying to parse them.
int exit_code = node::InitializeNodeWithArgs(&args, &exec_args, &errors);
for (const std::string& error : errors)
fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str());
if (exit_code != 0) {
return exit_code;
}
// Create a v8::Platform instance. `MultiIsolatePlatform::Create()` is a way
// to create a v8::Platform instance that Node.js can use when creating
// Worker threads. When no `MultiIsolatePlatform` instance is present,
// Worker threads are disabled.
std::unique_ptr<MultiIsolatePlatform> platform =
MultiIsolatePlatform::Create(4);
V8::InitializePlatform(platform.get());
V8::Initialize();
// See below for the contents of this function.
int ret = RunNodeInstance(platform.get(), args, exec_args);
V8::Dispose();
V8::ShutdownPlatform();
return ret;
} Состояние на экземпляр
Node.js имеет понятие «экземпляра Node.js», которое обычно называется node::Environment. Каждый node::Environment ассоциирован с:
- Ровно одним
v8::Isolate, т. е. одним экземпляром JS Engine, - Ровно одним
uv_loop_t, т. е. одной цикловой очередью, и - Некоторым количеством
v8::Contexts, но ровно одной главнойv8::Context. - Один экземпляр
node::IsolateData, содержащий информацию, которая может быть разделена несколькимиnode::Environments, использующими один и тот жеv8::Isolate. В настоящее время для этого сценария не проводятся тесты.
Для настройки v8::Isolate, необходимо предоставить v8::ArrayBuffer::Allocator. Одним из возможных вариантов является стандартный аллокатор Node.js, который можно создать с помощью node::ArrayBufferAllocator::Create(). Использование аллокатора Node.js позволяет вносить незначительные оптимизации производительности, когда плагины используют Node.js C++ Buffer API, и требуется для отслеживания 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;
// Set up a libuv event loop.
uv_loop_t loop;
int ret = uv_loop_init(&loop);
if (ret != 0) {
fprintf(stderr, "%s: Failed to initialize loop: %s\n",
args[0].c_str(),
uv_err_name(ret));
return 1;
}
std::shared_ptr<ArrayBufferAllocator> allocator =
ArrayBufferAllocator::Create();
Isolate* isolate = NewIsolate(allocator, &loop, platform);
if (isolate == nullptr) {
fprintf(stderr, "%s: Failed to initialize V8 Isolate\n", args[0].c_str());
return 1;
}
{
Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
// Create a node::IsolateData instance that will later be released using
// node::FreeIsolateData().
std::unique_ptr<IsolateData, decltype(&node::FreeIsolateData)> isolate_data(
node::CreateIsolateData(isolate, &loop, platform, allocator.get()),
node::FreeIsolateData);
// Set up a new v8::Context.
HandleScope handle_scope(isolate);
Local<Context> context = node::NewContext(isolate);
if (context.IsEmpty()) {
fprintf(stderr, "%s: Failed to initialize V8 Context\n", args[0].c_str());
return 1;
}
// The v8::Context needs to be entered when node::CreateEnvironment() and
// node::LoadEnvironment() are being called.
Context::Scope context_scope(context);
// Create a node::Environment instance that will later be released using
// node::FreeEnvironment().
std::unique_ptr<Environment, decltype(&node::FreeEnvironment)> env(
node::CreateEnvironment(isolate_data.get(), context, args, exec_args),
node::FreeEnvironment);
// Set up the Node.js instance for execution, and run code inside of it.
// There is also a variant that takes a callback and provides it with
// the `require` and `process` objects, so that it can manually compile
// and run scripts as needed.
// The `require` function inside this script does *not* access the file
// system, and can only load built-in Node.js modules.
// `module.createRequire()` is being used to create one that is able to
// load files from the disk, and uses the standard CommonJS file loader
// instead of the internal-only `require` function.
MaybeLocal<Value> loadenv_ret = node::LoadEnvironment(
env.get(),
"const publicRequire ="
" require('module').createRequire(process.cwd() + '/');"
"globalThis.require = publicRequire;"
"require('vm').runInThisContext(process.argv[1]);");
if (loadenv_ret.IsEmpty()) // There has been a JS exception.
return 1;
{
// SealHandleScope protects against handle leaks from callbacks.
SealHandleScope seal(isolate);
bool more;
do {
uv_run(&loop, UV_RUN_DEFAULT);
// V8 tasks on background threads may end up scheduling new tasks in the
// foreground, which in turn can keep the event loop going. For example,
// WebAssembly.compile() may do so.
platform->DrainTasks(isolate);
// If there are new tasks, continue.
more = uv_loop_alive(&loop);
if (more) continue;
// node::EmitBeforeExit() is used to emit the 'beforeExit' event on
// the `process` object.
node::EmitBeforeExit(env.get());
// 'beforeExit' can also schedule new work that keeps the event loop
// running.
more = uv_loop_alive(&loop);
} while (more == true);
}
// node::EmitExit() returns the current exit code.
exit_code = node::EmitExit(env.get());
// node::Stop() can be used to explicitly stop the event loop and keep
// further JavaScript from running. It can be called from any thread,
// and will act like worker.terminate() if called from another thread.
node::Stop(env.get());
}
// Unregister the Isolate with the platform and add a listener that is called
// when the Platform is done cleaning up any state it had associated with
// the Isolate.
bool platform_finished = false;
platform->AddIsolateFinishedCallback(isolate, [](void* data) {
*static_cast<bool*>(data) = true;
}, &platform_finished);
platform->UnregisterIsolate(isolate);
isolate->Dispose();
// Wait until the platform has cleaned up all relevant resources.
while (!platform_finished)
uv_run(&loop, UV_RUN_ONCE);
int err = uv_loop_close(&loop);
assert(err == 0);
return exit_code;
}
© Joyent, Inc. and other Node contributors
Licensed under the MIT License.
Node.js is a trademark of Joyent, Inc. and is used with its permission.
We are not endorsed by or affiliated with Joyent.
https://nodejs.org/dist/latest-v12.x/docs/api/embedding.html