Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ exclude = ["packages/common", "packages/demo", "packages/windows-quickjs", "pack
[workspace.dependencies]
windows = "0.62.2"

# napi_* resolve from the module that provides them: the host .exe for Node, this cdylib for an
# engine build, or the modules an embedder lists in NAPI_HOST_MODULES (React Native for Windows).
# See packages/vendor/napi-sys/src/functions.rs (load_all).
[patch.crates-io]
napi-sys = { path = "packages/vendor/napi-sys" }

[profile.dev]
debug = true

Expand Down
53 changes: 42 additions & 11 deletions packages/vendor/napi-sys/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,31 @@ pub use napi9::*;

#[cfg(any(windows, feature = "dyn-symbols"))]
pub(super) unsafe fn load_all() -> Result<libloading::Library, libloading::Error> {
// An embedder with no Node-API in its executable names the modules that provide it, separated
// by ';' and in priority order: each symbol comes from the first one that exports it. React
// Native for Windows has the engine half (js_native_api.h) in hermes.dll and needs the Node half
// (node_api.h: threadsafe functions, cleanup hooks, buffers) from the module that embeds the
// addon, so it sets NAPI_HOST_MODULES=<that module>;<hermes.dll>.
#[cfg(windows)]
if let Some(list) = std::env::var_os("NAPI_HOST_MODULES") {
let mut hosts = Vec::new();
for path in list.to_string_lossy().split(';').map(str::trim).filter(|p| !p.is_empty()) {
hosts.push(libloading::Library::from(libloading::os::windows::Library::new(path)?));
}
if !hosts.is_empty() {
// A symbol a library lacks keeps the value an earlier load gave it, so loading in reverse
// leaves each symbol from the first library that has it.
for host in hosts.iter().rev() {
load_groups(host)?;
}
let mut hosts = hosts.into_iter();
let first = hosts.next().unwrap();
// The other libraries must stay loaded for as long as their symbols are in use: for good.
hosts.for_each(std::mem::forget);
return Ok(first);
}
}

#[cfg(windows)]
let host = {
use std::os::windows::ffi::OsStringExt;
Expand Down Expand Up @@ -823,24 +848,30 @@ pub(super) unsafe fn load_all() -> Result<libloading::Library, libloading::Error
#[cfg(unix)]
let host = libloading::os::unix::Library::this().into();

napi1::load(&host)?;
load_groups(&host)?;
Ok(host)
}

#[cfg(any(windows, feature = "dyn-symbols"))]
unsafe fn load_groups(host: &libloading::Library) -> Result<(), libloading::Error> {
napi1::load(host)?;
#[cfg(feature = "napi2")]
napi2::load(&host)?;
napi2::load(host)?;
#[cfg(feature = "napi3")]
napi3::load(&host)?;
napi3::load(host)?;
#[cfg(feature = "napi4")]
napi4::load(&host)?;
napi4::load(host)?;
#[cfg(feature = "napi5")]
napi5::load(&host)?;
napi5::load(host)?;
#[cfg(feature = "napi6")]
napi6::load(&host)?;
napi6::load(host)?;
#[cfg(feature = "napi7")]
napi7::load(&host)?;
napi7::load(host)?;
#[cfg(feature = "napi8")]
napi8::load(&host)?;
napi8::load(host)?;
#[cfg(feature = "napi9")]
napi9::load(&host)?;
napi9::load(host)?;
#[cfg(feature = "experimental")]
experimental::load(&host)?;
Ok(host)
experimental::load(host)?;
Ok(())
}
13 changes: 13 additions & 0 deletions windows-napi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ The host executable's manifest must declare `<maxversiontested Id="10.0.18362.0"
`electron.exe` does and `node.exe` doesn't, so `XamlWindow` throws in plain Node. XAML events run
from the host's message loop, so a host with its own UI loop needs no `pumpMessages`.

## Embedding outside Node

Any host that provides Node-API can load the addon: load the `.node` file as a DLL and call its
`napi_register_module_v1(env, exports)`. The addon takes its `napi_*` functions from the host
executable, as in Node. A host whose executable doesn't export them sets `NAPI_HOST_MODULES`
before loading the addon: the modules that do, separated by `;`, in priority order. Each function
comes from the first module that exports it.

React Native for Windows is one: `@nativescript/react-native` runs the addon in a Hermes runtime,
where `hermes.dll` has the engine half of Node-API (`js_native_api.h`) and the module itself
implements Node's half (`node_api.h`: threadsafe functions, cleanup hooks, buffers). It sets
`NAPI_HOST_MODULES=<its DLL>;<hermes.dll>`.

## Development

```sh
Expand Down