Skip to content

libnode: Added C FFI compatible function to act as an entrypoint for embedded consumers #57846

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

alshdavid
Copy link

Motivation

Hi all,

I have been working on embedding Nodejs in my Rust application to act as a plugin runtime however, while libnode exposes a C++ API, it does not work with consumers that only have access to C FFI calls (like Rust).

Changes

  • Added node_start() function in node.cc to proxy node::Start()
    • Enables C bindings to be written for Nodejs

Examples

I created a repo that patches nodejs to add this function, generate static binaries (under GitHub releases) and offers a Rust crate that provides bindings. So far, all I need is this one function added to node.cc.

Usage:

use libnode_rs;

pub fn main() -> std::io::Result<()> {
  // Register a napi module and inject it into the JavaScript runtime
  libnode_rs::napi_module_register("my_native_extension", |env, exports| exports);

  libnode_rs::eval_blocking(r"
    console.log('Hello World')
    console.log(process._linkedBinding('my_native_extension'))
  ")
}

CC: #52289

@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/startup

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. labels Apr 12, 2025
@alshdavid alshdavid changed the title Added C function to act as an entrypoint for embedded consumers libnode: Added C FFI compatible function to act as an entrypoint for embedded consumers Apr 12, 2025
@vmoroz
Copy link
Member

vmoroz commented Apr 25, 2025

@alshdavid , this PR looks like to be related to this PR #54660.
I would appreciate your feedback on that PR and if it works for your scenario.
It is still work in progress and many things are subject to change. Your feedback may help shaping the new API.

Currently this new experimental C API is implemented in this libnode NuGet package (based on Node.js 20.x): https://www.nuget.org/packages/Microsoft.JavaScript.LibNode
We use it in https://github.com/microsoft/node-api-dotnet project.

@alshdavid
Copy link
Author

alshdavid commented Apr 28, 2025

@alshdavid , this PR looks like to be related to this PR #54660. I would appreciate your feedback on that PR and if it works for your scenario. It is still work in progress and many things are subject to change. Your feedback may help shaping the new API.

Currently this new experimental C API is implemented in this libnode NuGet package (based on Node.js 20.x): https://www.nuget.org/packages/Microsoft.JavaScript.LibNode We use it in https://github.com/microsoft/node-api-dotnet project.

This looks great and I welcome an expanded libnode API. In my case, all I need to get off the ground is a node_start function as that allows me to bootstrap a dynamically linked Nodejs instance and inject napi extensions.

Anything beyond that is a nice to have for my use case; e.g.

  • Allow a new nodejs instance to be started if a nodejs instance has been shut down.
    • basically allow node_start to be run multiple times
  • Consuming/supplying stdin/stdout/stderr to the Nodejs process
  • Support for multiple nodejs instances in one process
    • Or support multiple isolated Nodejs "vm"s in one process

@vmoroz
Copy link
Member

vmoroz commented Apr 29, 2025

@alshdavid , I believe that the upcoming embedding API should address the most of your requirements:

node_start function

The API has the node_embedding_main_run function that is a bit extended the Node.js node::Start functions. The extra parameters can be set to NULL.

  • Allow a new nodejs instance to be started if a nodejs instance has been shut down.
    • basically allow node_start to be run multiple times

We cannot do it. I guess this is a V8 limitation. It allows to initialize and then dispose the V8 platform only once. Thus, the Node.js must do the same. Instead of starting and stopping the whole Node.js, you should rather change your usage to create/delete the node_embedding_pltaform only once, and then create/delete the node_embedding_runtime instances as many times as you like. Each node_embedding_runtime is bound to a single node::Environment which in turn is bound to a single V8 Isolate. Node.js supports multiple node::Environment instances. E.g. each worker thread is a node::Environment instance. The only limitation that we have is that the Inspector (JS debugger) can be associated only with a single top level node::Environment or node_embedding_runtime.

  • Consuming/supplying stdin/stdout/stderr to the Nodejs process

I need more info to understand this scenario. The shared libnode is not a Node.js process. Should it be your process that uses libnode to take care of the stdin/stdout/stderr?

  • Support for multiple nodejs instances in one process
    - Or support multiple isolated Nodejs "vm"s in one process

See the node_embedding_runtime mentioned above. The PR has a number of unit tests that should show the API usage.

@alshdavid
Copy link
Author

alshdavid commented Apr 30, 2025

@vmoroz thanks for the details!

I have started working on a Rust crate that adds bindings for the new C api you have in your PR. I am working off of local builds of libnode.so from your branch (is libnode.a possible? I've had no success trying to build to a static library).

Will hopefully be able to fetch the prebuilt library files from official urls at some point.

It currently includes all of the C n-api bindings and a few of the embedder APIs (like napi_module_register).

https://github.com/alshdavid/libnode_sys
https://crates.io/crates/libnode_sys

I'll close my PR given #54660 does what I need and more.

I need more info to understand this scenario. The shared libnode is not a Node.js process. Should it be your process that uses libnode to take care of the stdin/stdout/stderr?

Sorry, poor choice of words. The internal node.js instance inherits stdin/out/err from the host process - meaning a console.log() running in the embedded Node.js instance will print to the terminal directly. It's not actually a requirement for my use case but it would be cool if stdin/out/err could be wrapped by the embedder to help silencing error messages / handling them within the embedding context.

@alshdavid alshdavid closed this Apr 30, 2025
@vmoroz
Copy link
Member

vmoroz commented May 2, 2025

@alshdavid , regarding the stdin/stdout/stderr, I believe it is all normal app behavior. Any library can use them without any additional work. I.e. the JS code that works with libnode.so can use console.log() or any other IO without any additional configuration.

@alshdavid alshdavid deleted the alsh/add-c-start-function branch May 2, 2025 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants