Skip to content

API de incrustación en C++

Node.js proporciona una serie de API de C++ que se pueden utilizar para ejecutar JavaScript en un entorno Node.js desde otro software de C++.

La documentación de estas API se puede encontrar en src/node.h en el árbol de código fuente de Node.js. Además de las API expuestas por Node.js, el API de incrustación de V8 proporciona algunos conceptos necesarios.

Dado que el uso de Node.js como biblioteca incrustada es diferente a escribir código que es ejecutado por Node.js, los cambios que rompen la compatibilidad no siguen la política de obsolescencia típica de Node.js y pueden ocurrir en cada lanzamiento principal semántico sin previo aviso.

Ejemplo de aplicación de incrustación

Las siguientes secciones proporcionarán una descripción general de cómo utilizar estas API para crear una aplicación desde cero que realizará el equivalente de node -e \<code\>, es decir, que tomará un fragmento de JavaScript y lo ejecutará en un entorno específico de Node.js.

El código completo se puede encontrar en el árbol de código fuente de Node.js.

Configuración de un estado por proceso

Node.js requiere cierta gestión del estado por proceso para funcionar:

  • Análisis de argumentos para las opciones de CLI de Node.js,
  • Requisitos por proceso de V8, como una instancia de v8::Platform.

El siguiente ejemplo muestra cómo se pueden configurar. Algunos nombres de clase son de los espacios de nombres C++ node y v8, respectivamente.

C++
int main(int argc, char** argv) {
  argv = uv_setup_args(argc, argv);
  std::vector<std::string> args(argv, argv + argc);
  // Analiza las opciones de CLI de Node.js e imprime cualquier error que se haya
  // producido al intentar analizarlas.
  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();
  }

  // Crea una instancia de v8::Platform. `MultiIsolatePlatform::Create()` es una forma
  // de crear una instancia de v8::Platform que Node.js puede usar al crear
  // hilos de Worker. Cuando no hay ninguna instancia de `MultiIsolatePlatform` presente,
  // los hilos de Worker están deshabilitados.
  std::unique_ptr<MultiIsolatePlatform> platform =
      MultiIsolatePlatform::Create(4);
  V8::InitializePlatform(platform.get());
  V8::Initialize();

  // Consulta a continuación el contenido de esta función.
  int ret = RunNodeInstance(
      platform.get(), result->args(), result->exec_args());

  V8::Dispose();
  V8::DisposePlatform();

  node::TearDownOncePerProcess();
  return ret;
}

Configuración de un estado por instancia

[Historial]

VersiónCambios
v15.0.0Se agregaron las utilidades CommonEnvironmentSetup y SpinEventLoop.

Node.js tiene un concepto de "instancia de Node.js", que comúnmente se conoce como node::Environment. Cada node::Environment está asociado con:

  • Exactamente un v8::Isolate, es decir, una instancia de JS Engine,
  • Exactamente un uv_loop_t, es decir, un bucle de eventos,
  • Un número de v8::Contexts, pero exactamente un v8::Context principal, y
  • Una instancia node::IsolateData que contiene información que puede ser compartida por múltiples node::Environments. El incrustador debe asegurarse de que node::IsolateData solo se comparta entre node::Environments que usan el mismo v8::Isolate, Node.js no realiza esta verificación.

Para configurar un v8::Isolate, se debe proporcionar un v8::ArrayBuffer::Allocator. Una posible opción es el asignador predeterminado de Node.js, que se puede crear a través de node::ArrayBufferAllocator::Create(). El uso del asignador de Node.js permite optimizaciones de rendimiento menores cuando los complementos usan la API C++ Buffer de Node.js, y es necesario para rastrear la memoria ArrayBuffer en process.memoryUsage().

Además, cada v8::Isolate que se utiliza para una instancia de Node.js necesita registrarse y anular su registro con la instancia MultiIsolatePlatform, si se está utilizando una, para que la plataforma sepa qué bucle de eventos utilizar para las tareas programadas por el v8::Isolate.

La función auxiliar node::NewIsolate() crea un v8::Isolate, lo configura con algunos enlaces específicos de Node.js (por ejemplo, el controlador de errores de Node.js) y lo registra automáticamente con la plataforma.

C++
int RunNodeInstance(MultiIsolatePlatform* platform,
                    const std::vector<std::string>& args,
                    const std::vector<std::string>& exec_args) {
  int exit_code = 0;

  // Configurar un bucle de eventos libuv, v8::Isolate y el entorno de Node.js.
  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);
    // El v8::Context debe ingresarse cuando se llaman a node::CreateEnvironment() y
    // node::LoadEnvironment().
    Context::Scope context_scope(setup->context());

    // Configurar la instancia de Node.js para la ejecución y ejecutar el código dentro de ella.
    // También hay una variante que toma una devolución de llamada y le proporciona
    // los objetos `require` y `process`, para que pueda compilar manualmente
    // y ejecutar scripts según sea necesario.
    // La función `require` dentro de este script *no* accede al sistema de archivos
    // y solo puede cargar módulos integrados de Node.js.
    // `module.createRequire()` se está utilizando para crear uno que pueda
    // cargar archivos desde el disco y utiliza el cargador de archivos CommonJS
    // estándar en lugar de la función `require` solo interna.
    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())  // Ha habido una excepción JS.
      return 1;

    exit_code = node::SpinEventLoop(env).FromMaybe(1);

    // node::Stop() se puede utilizar para detener explícitamente el bucle de eventos
    // y evitar que se ejecute más JavaScript. Se puede llamar desde cualquier hilo,
    // y actuará como worker.terminate() si se llama desde otro hilo.
    node::Stop(env);
  }

  return exit_code;
}