embeddings

C++ Embedder API Overview

Node.js offers C++ APIs to let you run JavaScript from your C++ programs, allowing you to integrate Node.js's powerful JavaScript engine into your own software.

Key Concepts

Node.js Embedder APIs

  • Node.js Embedder APIs: Functions in src/node.h that let you interact with Node.js from C++.

V8 Embedder API

  • V8 Embedder API: Concepts from V8 (the JavaScript engine used by Node.js) that are necessary for using the Node.js Embedder APIs.

Core Concepts Explained

Node.js Embedder APIs

Think of these as tools that allow your C++ code to control and interact with Node.js. They let you:

  • Start and stop the Node.js runtime

  • Execute JavaScript code

  • Load and require Node.js modules

  • Handle events and callbacks

V8 Embedder API

The V8 Embedder API provides basic concepts needed to use the Node.js Embedder APIs, such as:

  • Isolate: A sandboxed environment where JavaScript code executes.

  • Context: A specific environment within an isolate that holds variables and properties.

  • HandleScope: A block of code within which JavaScript handles (pointers to JavaScript objects) are valid and can be manipulated.

Real-World Example

Embedding Node.js in a C++ GUI Application

  • You could use the Node.js Embedder APIs to integrate a JavaScript-based UI (developed in Node.js) into your C++ desktop application.

  • By leveraging Node.js's rich ecosystem of JavaScript libraries, you can create a dynamic and interactive user interface for your C++ application.

Code Implementation

#include <node.h>
#include <v8.h>

using namespace v8;

int main() {
  // Initialize the Node.js runtime
  Node::Init();

  // Create an isolate and context for JavaScript execution
  Isolate* isolate = Isolate::New();
  HandleScope handle_scope(isolate);
  Local<Context> context = Context::New(isolate);

  // Execute a JavaScript script
  Local<Script> script = Script::Compile(context, String::NewFromUtf8(isolate, "console.log('Hello, world!')").ToLocalChecked());
  script->Run(context).ToLocalChecked();

  // Clean up
  isolate->Dispose();

  return 0;
}

Potential Applications

  • Creating interactive GUIs for C++ applications

  • Integrating JavaScript-based data analysis and machine learning into C++ programs

  • Extending C++ software with custom JavaScript modules

  • Interfacing with JavaScript-based web services and APIs from C++


Simplified Explanation of Node.js Embedding API:

What is Embedding?

Embedding allows you to run JavaScript code in your own applications without using the Node.js runtime. This is useful for tasks like executing JavaScript in other programs or extending existing applications with JavaScript capabilities.

Node.js Embedding API:

The Node.js Embedding API provides functions and classes that allow you to:

  • Create an embedded Node.js environment.

  • Execute JavaScript code within that environment.

  • Access and manipulate JavaScript values.

  • Communicate between the embedded environment and your application.

Real-World Applications:

  • Custom JavaScript processors: Embed a Node.js environment into your application to process JavaScript data on the fly.

  • Extension of existing programs: Enhance existing programs by adding JavaScript functionality, such as data validation or custom scripting.

  • Hosting JavaScript applications: Create a custom runtime environment for hosting JavaScript applications within your own platform.

Example Code Implementation:

// Create an embedded Node.js environment
const env = new Node.js.Environment();

// Execute JavaScript code
env.execute('console.log("Hello from Node.js")');

// Get the result
const result = env.getReturnValue();

// Print the result
console.log(result);

Improved Code Example:

// Embed JavaScript into a web server
const http = require("http");
const Node = require("nodejs");

const env = new Node.Environment();
const script = `
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello from Node.js');
`;

const server = http.createServer((req, res) => {
  env.run(script, { res });
});

server.listen(8080);

This script creates a simple web server where the response is dynamically generated by running the provided JavaScript code in the embedded environment.


Setting up Per-Process State in Node.js

What is Per-Process State?

Every time Node.js starts, it needs to set up some basic information and settings that apply to the entire process. This includes things like:

  • Command-line arguments

  • V8 engine settings (V8 is the JavaScript engine used by Node.js)

How to Set Up Per-Process State

The following steps show how to set up per-process state in Node.js:

  1. Initialize V8 and Node.js:

    • Initialize the V8 engine and Node.js core modules using node::InitializeOncePerProcess(). This function takes command-line arguments as input and returns any errors that occurred during initialization.

    • Check for errors and exit the process if necessary.

  2. Create a V8 Platform:

    • Create a V8 platform instance using MultiIsolatePlatform::Create(). This instance allows Node.js to create multiple V8 instances (called isolates), enabling features like worker threads.

  3. Run Node.js Instance:

    • Call RunNodeInstance() to run a Node.js instance using the V8 platform and command-line arguments. This starts the Node.js event loop and executes the main script.

  4. Clean Up:

    • Once the Node.js instance has finished running, clean up memory and resources by calling V8::Dispose(), V8::DisposePlatform(), node::TearDownOncePerProcess().

Real-World Applications

Per-process state management is essential for running Node.js in various scenarios:

  • Command-Line Applications: Node.js scripts can be run from the command line, and arguments need to be parsed and processed.

  • Electron Applications: Electron uses Node.js to create desktop applications, which require per-process state management to isolate each app window.

  • Web Servers: Node.js can be used to create web servers, and each request needs to be processed in its own isolate, which requires per-process state initialization.

Example Code

int main(int argc, char** argv) {
  argv = uv_setup_args(argc, argv);
  std::vector<std::string> args(argv, argv + argc);
  auto result = node::InitializeOncePerProcess(args, {});

  for (const std::string& error : result->errors())
    fprintf(stderr, "%s: %s
", args[0].c_str(), error.c_str());
  if (result->early_return() != 0) {
    return result->exit_code();
  }

  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;
}

This example sets up per-process state for a command-line Node.js application. It parses arguments, initializes V8 and Node.js, creates a V8 platform, runs a Node.js instance, and cleans up resources when finished.


Node.js Instances

  • A Node.js instance is like a sandbox that runs JavaScript code.

  • Each instance has its own memory and event loop.

  • Node.js instances are created using the NewIsolate() function.

Memory Allocation

  • Node.js uses a specific memory allocator to manage memory used by ArrayBuffer objects.

  • Using the Node.js allocator is recommended for performance and memory tracking.

  • The ArrayBufferAllocator::Create() function can be used to create the allocator.

Platform Integration

  • Node.js instances need to be registered with the MultiIsolatePlatform.

  • This allows the platform to manage the event loop for each instance.

  • The NewIsolate() function automatically registers the instance with the platform.

Running Node.js Code

  • To run JavaScript code in a Node.js instance, you need to:

    • Create an event loop, isolate, and environment.

    • Load the script into the environment.

    • Start the event loop.

    • Wait for the event loop to finish before exiting.

Real-World Applications

  • Node.js instances can be used to run JavaScript code in a variety of applications, such as:

    • Web servers

    • Command-line scripts

    • Mobile applications

    • Desktop applications

Code Example

The following code shows how to create a Node.js instance and run code in it:

#include <node.h>
#include <v8.h>

int main(int argc, char* argv[]) {
  // Create a Node.js instance.
  std::unique_ptr<node::Environment> env = node::NewIsolate();

  // Load the script into the instance.
  auto maybe_script = node::LoadScript(env.get(), "script.js");
  if (maybe_script.IsEmpty()) {
    // Handle script loading error.
  }

  // Run the script in the instance.
  auto maybe_result = node::RunScript(env.get(), maybe_script.ToLocalChecked());
  if (maybe_result.IsEmpty()) {
    // Handle script execution error.
  }

  // Stop the event loop.
  node::Stop(env.get());

  return 0;
}