Skip to content

Commit afca75e

Browse files
authored
Create functions from closures (#811)
feat(neon): Create functions from closures
1 parent 4581958 commit afca75e

File tree

9 files changed

+267
-71
lines changed

9 files changed

+267
-71
lines changed

crates/neon-runtime/src/napi/bindings/functions.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ mod napi4 {
290290
#[cfg(feature = "napi-5")]
291291
mod napi5 {
292292
use super::super::types::*;
293+
use std::ffi::c_void;
293294

294295
generate!(
295296
extern "C" {
@@ -298,6 +299,15 @@ mod napi5 {
298299
fn get_date_value(env: Env, value: Value, result: *mut f64) -> Status;
299300

300301
fn is_date(env: Env, value: Value, result: *mut bool) -> Status;
302+
303+
fn add_finalizer(
304+
env: Env,
305+
js_object: Value,
306+
native_object: *mut c_void,
307+
finalize_cb: Finalize,
308+
finalize_hint: *mut c_void,
309+
result: Ref,
310+
) -> Status;
301311
}
302312
);
303313
}

crates/neon-runtime/src/napi/call.rs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,6 @@ impl Arguments {
4242
}
4343
}
4444

45-
#[repr(C)]
46-
pub struct CCallback {
47-
pub static_callback: *mut c_void,
48-
pub dynamic_callback: *mut c_void,
49-
}
50-
51-
impl Default for CCallback {
52-
fn default() -> Self {
53-
CCallback {
54-
static_callback: null_mut(),
55-
dynamic_callback: null_mut(),
56-
}
57-
}
58-
}
59-
6045
pub unsafe fn is_construct(env: Env, info: FunctionCallbackInfo) -> bool {
6146
let mut target: MaybeUninit<Local> = MaybeUninit::zeroed();
6247

crates/neon-runtime/src/napi/fun.rs

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,86 @@
11
//! Facilities for working with JS functions.
22
3+
use std::mem::MaybeUninit;
34
use std::os::raw::c_void;
4-
use std::ptr::null;
5+
use std::ptr;
56

6-
use crate::call::CCallback;
77
use crate::napi::bindings as napi;
88
use crate::raw::{Env, Local};
99

10-
/// Mutates the `out` argument provided to refer to a newly created `v8::Function`. Returns
11-
/// `false` if the value couldn't be created.
12-
pub unsafe fn new(out: &mut Local, env: Env, callback: CCallback) -> bool {
10+
pub unsafe fn new<F>(env: Env, name: &str, callback: F) -> Result<Local, napi::Status>
11+
where
12+
F: Fn(Env, napi::CallbackInfo) -> Local + 'static,
13+
{
14+
let mut out = MaybeUninit::uninit();
15+
let data = Box::into_raw(Box::new(callback));
1316
let status = napi::create_function(
1417
env,
15-
null(),
16-
0,
17-
Some(std::mem::transmute(callback.static_callback)),
18-
callback.dynamic_callback,
19-
out as *mut Local,
18+
name.as_ptr().cast(),
19+
name.len(),
20+
Some(call_boxed::<F>),
21+
data.cast(),
22+
out.as_mut_ptr(),
2023
);
2124

22-
status == napi::Status::Ok
25+
if status == napi::Status::PendingException {
26+
Box::from_raw(data);
27+
28+
return Err(status);
29+
}
30+
31+
assert_eq!(status, napi::Status::Ok);
32+
33+
let out = out.assume_init();
34+
35+
#[cfg(feature = "napi-5")]
36+
{
37+
unsafe extern "C" fn drop_function<F>(
38+
_env: Env,
39+
_finalize_data: *mut c_void,
40+
finalize_hint: *mut c_void,
41+
) {
42+
Box::from_raw(finalize_hint.cast::<F>());
43+
}
44+
45+
let status = napi::add_finalizer(
46+
env,
47+
out,
48+
ptr::null_mut(),
49+
Some(drop_function::<F>),
50+
data.cast(),
51+
ptr::null_mut(),
52+
);
53+
54+
// If adding the finalizer fails the closure will leak, but it would
55+
// be unsafe to drop it because there's no guarantee V8 won't use the
56+
// pointer.
57+
assert_eq!(status, napi::Status::Ok);
58+
}
59+
60+
Ok(out)
2361
}
2462

25-
pub unsafe fn get_dynamic_callback(_env: Env, data: *mut c_void) -> *mut c_void {
26-
data
63+
// C ABI compatible function for invoking a boxed closure from the data field
64+
// of a Node-API JavaScript function
65+
unsafe extern "C" fn call_boxed<F>(env: Env, info: napi::CallbackInfo) -> Local
66+
where
67+
F: Fn(Env, napi::CallbackInfo) -> Local + 'static,
68+
{
69+
let mut data = MaybeUninit::uninit();
70+
let status = napi::get_cb_info(
71+
env,
72+
info,
73+
ptr::null_mut(),
74+
ptr::null_mut(),
75+
ptr::null_mut(),
76+
data.as_mut_ptr(),
77+
);
78+
79+
assert_eq!(status, napi::Status::Ok);
80+
81+
let callback = &*data.assume_init().cast::<F>();
82+
83+
callback(env, info)
2784
}
2885

2986
pub unsafe fn call(

src/context/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ use std;
184184
use std::cell::RefCell;
185185
use std::convert::Into;
186186
use std::marker::PhantomData;
187+
#[cfg(feature = "legacy-runtime")]
187188
use std::os::raw::c_void;
188189
use std::panic::UnwindSafe;
189190

@@ -196,6 +197,15 @@ pub(crate) struct CallbackInfo<'a> {
196197
}
197198

198199
impl CallbackInfo<'_> {
200+
#[cfg(feature = "napi-1")]
201+
pub unsafe fn new(info: raw::FunctionCallbackInfo) -> Self {
202+
Self {
203+
info,
204+
_lifetime: PhantomData,
205+
}
206+
}
207+
208+
#[cfg(feature = "legacy-runtime")]
199209
pub fn data(&self, env: Env) -> *mut c_void {
200210
unsafe {
201211
let mut raw_data: *mut c_void = std::mem::zeroed();
@@ -204,6 +214,7 @@ impl CallbackInfo<'_> {
204214
}
205215
}
206216

217+
#[cfg(feature = "legacy-runtime")]
207218
pub unsafe fn with_cx<T: This, U, F: for<'a> FnOnce(CallContext<'a, T>) -> U>(
208219
&self,
209220
env: Env,
@@ -695,6 +706,7 @@ impl<'a> ModuleContext<'a> {
695706
Scope::with(env, |scope| f(ModuleContext { scope, exports }))
696707
}
697708

709+
#[cfg(not(feature = "napi-5"))]
698710
/// Convenience method for exporting a Neon function from a module.
699711
pub fn export_function<T: Value>(
700712
&mut self,
@@ -706,6 +718,18 @@ impl<'a> ModuleContext<'a> {
706718
Ok(())
707719
}
708720

721+
#[cfg(feature = "napi-5")]
722+
/// Convenience method for exporting a Neon function from a module.
723+
pub fn export_function<F, V>(&mut self, key: &str, f: F) -> NeonResult<()>
724+
where
725+
F: Fn(FunctionContext) -> JsResult<V> + 'static,
726+
V: Value,
727+
{
728+
let value = JsFunction::new(self, f)?.upcast::<JsValue>();
729+
self.exports.set(self, key, value)?;
730+
Ok(())
731+
}
732+
709733
#[cfg(feature = "legacy-runtime")]
710734
/// Convenience method for exporting a Neon class constructor from a module.
711735
pub fn export_class<T: Class>(&mut self, key: &str) -> NeonResult<()> {

src/types/internal.rs

Lines changed: 17 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
use super::Value;
22
use crate::context::internal::Env;
3-
use crate::context::{CallbackInfo, FunctionContext};
3+
#[cfg(feature = "legacy-runtime")]
4+
use crate::context::CallbackInfo;
5+
#[cfg(feature = "legacy-runtime")]
6+
use crate::context::FunctionContext;
7+
#[cfg(feature = "legacy-runtime")]
48
use crate::result::JsResult;
5-
use crate::types::error::convert_panics;
6-
use crate::types::{Handle, JsObject, Managed};
9+
use crate::types::{Handle, Managed};
710
use neon_runtime;
11+
#[cfg(feature = "legacy-runtime")]
812
use neon_runtime::call::CCallback;
913
use neon_runtime::raw;
10-
use std::mem;
14+
#[cfg(feature = "legacy-runtime")]
1115
use std::os::raw::c_void;
1216

1317
pub trait ValueInternal: Managed + 'static {
@@ -28,47 +32,24 @@ pub trait ValueInternal: Managed + 'static {
2832
}
2933
}
3034

35+
#[cfg(feature = "legacy-runtime")]
3136
#[repr(C)]
3237
pub struct FunctionCallback<T: Value>(pub fn(FunctionContext) -> JsResult<T>);
3338

3439
#[cfg(feature = "legacy-runtime")]
3540
impl<T: Value> Callback<()> for FunctionCallback<T> {
3641
extern "C" fn invoke(env: Env, info: CallbackInfo<'_>) {
37-
unsafe {
38-
info.with_cx::<JsObject, _, _>(env, |cx| {
39-
let data = info.data(env);
40-
let dynamic_callback: fn(FunctionContext) -> JsResult<T> =
41-
mem::transmute(neon_runtime::fun::get_dynamic_callback(env.to_raw(), data));
42-
if let Ok(value) = convert_panics(env, || dynamic_callback(cx)) {
43-
info.set_return(value);
44-
}
45-
})
46-
}
47-
}
42+
use crate::types::error::convert_panics;
43+
use crate::types::JsObject;
4844

49-
fn into_ptr(self) -> *mut c_void {
50-
self.0 as *mut _
51-
}
52-
}
53-
54-
#[cfg(feature = "napi-1")]
55-
impl<T: Value> Callback<raw::Local> for FunctionCallback<T> {
56-
extern "C" fn invoke(env: Env, info: CallbackInfo<'_>) -> raw::Local {
5745
unsafe {
5846
info.with_cx::<JsObject, _, _>(env, |cx| {
5947
let data = info.data(env);
60-
let dynamic_callback: fn(FunctionContext) -> JsResult<T> =
61-
mem::transmute(neon_runtime::fun::get_dynamic_callback(env.to_raw(), data));
48+
let dynamic_callback: fn(FunctionContext) -> JsResult<T> = std::mem::transmute(
49+
neon_runtime::fun::get_dynamic_callback(env.to_raw(), data),
50+
);
6251
if let Ok(value) = convert_panics(env, || dynamic_callback(cx)) {
63-
value.to_raw()
64-
} else {
65-
// We do not have a Js Value to return, most likely due to an exception.
66-
// If we are in a throwing state, constructing a Js Value would be invalid.
67-
// While not explicitly written, the N-API documentation includes many examples
68-
// of returning `NULL` when a native function does not return a value.
69-
// Note, `raw::Local` in this context is a type alias for `*mut napi_value` and not a struct
70-
// https://nodejs.org/api/n-api.html#n_api_napi_create_function
71-
std::ptr::null_mut()
52+
info.set_return(value);
7253
}
7354
})
7455
}
@@ -83,6 +64,7 @@ impl<T: Value> Callback<raw::Local> for FunctionCallback<T> {
8364
/// This type makes it possible to export a dynamically computed Rust function
8465
/// as a pair of 1) a raw pointer to the dynamically computed function, and 2)
8566
/// a static function that knows how to transmute that raw pointer and call it.
67+
#[cfg(feature = "legacy-runtime")]
8668
pub(crate) trait Callback<T: Clone + Copy + Sized>: Sized {
8769
/// Extracts the computed Rust function and invokes it. The Neon runtime
8870
/// ensures that the computed function is provided as the extra data field,
@@ -91,8 +73,6 @@ pub(crate) trait Callback<T: Clone + Copy + Sized>: Sized {
9173

9274
/// See `invoke`. This is used by the non-n-api implementation, so that every impl for this
9375
/// trait doesn't need to provide two versions of `invoke`.
94-
#[cfg(feature = "legacy-runtime")]
95-
#[doc(hidden)]
9676
extern "C" fn invoke_compat(info: CallbackInfo<'_>) -> T {
9777
Self::invoke(Env::current(), info)
9878
}
@@ -103,12 +83,8 @@ pub(crate) trait Callback<T: Clone + Copy + Sized>: Sized {
10383
/// Exports the callback as a pair consisting of the static `Self::invoke`
10484
/// method and the computed callback, both converted to raw void pointers.
10585
fn into_c_callback(self) -> CCallback {
106-
#[cfg(feature = "napi-1")]
107-
let invoke = Self::invoke;
108-
#[cfg(feature = "legacy-runtime")]
109-
let invoke = Self::invoke_compat;
11086
CCallback {
111-
static_callback: unsafe { mem::transmute(invoke as usize) },
87+
static_callback: unsafe { std::mem::transmute(Self::invoke_compat as usize) },
11288
dynamic_callback: self.into_ptr(),
11389
}
11490
}

0 commit comments

Comments
 (0)