feat(napi): implement get_js_function

This commit is contained in:
LongYinan 2022-01-23 18:17:00 +08:00
parent 85b6b099a8
commit 16f808276d
No known key found for this signature in database
GPG key ID: C3666B7FC82ADAD7
7 changed files with 85 additions and 17 deletions

View file

@ -300,10 +300,7 @@ impl NapiFn {
let module_register_name = get_register_ident(&name_str); let module_register_name = get_register_ident(&name_str);
let intermediate_ident = get_intermediate_ident(&name_str); let intermediate_ident = get_intermediate_ident(&name_str);
let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref()); let js_mod_ident = js_mod_to_token_stream(self.js_mod.as_ref());
let cb_name = Ident::new( let cb_name = Ident::new(&format!("{}_js_function", name_str), Span::call_site());
&format!("__register__fn__{}_callback__", name_str),
Span::call_site(),
);
quote! { quote! {
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[allow(clippy::all)] #[allow(clippy::all)]
@ -322,7 +319,7 @@ impl NapiFn {
"Failed to register function `{}`", "Failed to register function `{}`",
#name_str, #name_str,
)?; )?;
napi::bindgen_prelude::register_js_function(#js_name, env, #cb_name, Some(#intermediate_ident));
Ok(fn_ptr) Ok(fn_ptr)
} }

View file

@ -6,7 +6,9 @@ use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use crate::{check_status, check_status_or_throw, sys, JsError, Property, Result}; use crate::{
check_status, check_status_or_throw, sys, JsError, JsFunction, Property, Result, Value, ValueType,
};
pub type ExportRegisterCallback = unsafe fn(sys::napi_env) -> Result<sys::napi_value>; pub type ExportRegisterCallback = unsafe fn(sys::napi_env) -> Result<sys::napi_value>;
pub type ModuleExportsCallback = pub type ModuleExportsCallback =
@ -82,12 +84,18 @@ type ModuleClassProperty = PersistedSingleThreadHashMap<
HashMap<Option<&'static str>, (&'static str, Vec<Property>)>, HashMap<Option<&'static str>, (&'static str, Vec<Property>)>,
>; >;
type FnRegisterMap = PersistedSingleThreadHashMap<
ExportRegisterCallback,
(sys::napi_env, sys::napi_callback, &'static str),
>;
unsafe impl<K, V> Send for PersistedSingleThreadHashMap<K, V> {} unsafe impl<K, V> Send for PersistedSingleThreadHashMap<K, V> {}
unsafe impl<K, V> Sync for PersistedSingleThreadHashMap<K, V> {} unsafe impl<K, V> Sync for PersistedSingleThreadHashMap<K, V> {}
lazy_static! { lazy_static! {
static ref MODULE_REGISTER_CALLBACK: ModuleRegisterCallback = Default::default(); static ref MODULE_REGISTER_CALLBACK: ModuleRegisterCallback = Default::default();
static ref MODULE_CLASS_PROPERTIES: ModuleClassProperty = Default::default(); static ref MODULE_CLASS_PROPERTIES: ModuleClassProperty = Default::default();
static ref FN_REGISTER_MAP: FnRegisterMap = Default::default();
} }
#[cfg(feature = "compat-mode")] #[cfg(feature = "compat-mode")]
@ -103,6 +111,7 @@ thread_local! {
>> = Default::default(); >> = Default::default();
} }
#[doc(hidden)]
pub fn get_class_constructor(js_name: &'static str) -> Option<sys::napi_ref> { pub fn get_class_constructor(js_name: &'static str) -> Option<sys::napi_ref> {
REGISTERED_CLASSES.with(|registered_classes| { REGISTERED_CLASSES.with(|registered_classes| {
let classes = registered_classes.borrow(); let classes = registered_classes.borrow();
@ -110,12 +119,14 @@ pub fn get_class_constructor(js_name: &'static str) -> Option<sys::napi_ref> {
}) })
} }
#[doc(hidden)]
#[cfg(feature = "compat-mode")] #[cfg(feature = "compat-mode")]
// compatibility for #[module_exports] // compatibility for #[module_exports]
pub fn register_module_exports(callback: ModuleExportsCallback) { pub fn register_module_exports(callback: ModuleExportsCallback) {
MODULE_EXPORTS.push(callback); MODULE_EXPORTS.push(callback);
} }
#[doc(hidden)]
pub fn register_module_export( pub fn register_module_export(
js_mod: Option<&'static str>, js_mod: Option<&'static str>,
name: &'static str, name: &'static str,
@ -124,6 +135,17 @@ pub fn register_module_export(
MODULE_REGISTER_CALLBACK.push((js_mod, (name, cb))); MODULE_REGISTER_CALLBACK.push((js_mod, (name, cb)));
} }
#[doc(hidden)]
pub fn register_js_function(
name: &'static str,
env: sys::napi_env,
cb: ExportRegisterCallback,
c_fn: sys::napi_callback,
) {
FN_REGISTER_MAP.borrow_mut().insert(cb, (env, c_fn, name));
}
#[doc(hidden)]
pub fn register_class( pub fn register_class(
rust_name: &'static str, rust_name: &'static str,
js_mod: Option<&'static str>, js_mod: Option<&'static str>,
@ -138,6 +160,40 @@ pub fn register_class(
val.1.extend(props.into_iter()); val.1.extend(props.into_iter());
} }
#[inline]
pub fn get_js_function(raw_fn: ExportRegisterCallback) -> Result<JsFunction> {
FN_REGISTER_MAP
.borrow_mut()
.get(&raw_fn)
.and_then(|(env, cb, name)| {
let mut function = ptr::null_mut();
let name_len = name.len() - 1;
let fn_name = unsafe { CStr::from_bytes_with_nul_unchecked(name.as_bytes()) };
check_status!(unsafe {
sys::napi_create_function(
*env,
fn_name.as_ptr(),
name_len,
*cb,
ptr::null_mut(),
&mut function,
)
})
.ok()?;
Some(JsFunction(Value {
env: *env,
value: function,
value_type: ValueType::Function,
}))
})
.ok_or_else(|| {
crate::Error::new(
crate::Status::InvalidArg,
"JavaScript function does not exists".to_owned(),
)
})
}
#[no_mangle] #[no_mangle]
unsafe extern "C" fn napi_register_module_v1( unsafe extern "C" fn napi_register_module_v1(
env: sys::napi_env, env: sys::napi_env,
@ -191,17 +247,13 @@ unsafe extern "C" fn napi_register_module_v1(
let js_name = unsafe { CStr::from_bytes_with_nul_unchecked(name.as_bytes()) }; let js_name = unsafe { CStr::from_bytes_with_nul_unchecked(name.as_bytes()) };
unsafe { unsafe {
if let Err(e) = callback(env).and_then(|v| { if let Err(e) = callback(env).and_then(|v| {
check_status!( let exported_object = if exports_js_mod.is_null() {
sys::napi_set_named_property(
env,
if exports_js_mod.is_null() {
exports exports
} else { } else {
exports_js_mod exports_js_mod
}, };
js_name.as_ptr(), check_status!(
v sys::napi_set_named_property(env, exported_object, js_name.as_ptr(), v),
),
"Failed to register export `{}`", "Failed to register export `{}`",
name, name,
) )

View file

@ -38,6 +38,7 @@ Generated by [AVA](https://avajs.dev).
export function optionOnly(callback: (arg0?: string | undefined | null) => void): void␊ export function optionOnly(callback: (arg0?: string | undefined | null) => void): void␊
/** napi = { version = 2, features = ["serde-json"] } */␊ /** napi = { version = 2, features = ["serde-json"] } */␊
export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void␊ export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void␊
export function returnJsFunction(): (...args: any[]) => any␊
export function eitherStringOrNumber(input: string | number): number␊ export function eitherStringOrNumber(input: string | number): number␊
export function returnEither(input: number): string | number␊ export function returnEither(input: number): string | number␊
export function either3(input: string | number | boolean): number␊ export function either3(input: string | number | boolean): number␊

View file

@ -77,6 +77,7 @@ import {
JsClassForEither, JsClassForEither,
receiveMutClassOrNumber, receiveMutClassOrNumber,
getStrFromObject, getStrFromObject,
returnJsFunction,
} from '../' } from '../'
test('export const', (t) => { test('export const', (t) => {
@ -193,6 +194,16 @@ test('callback', (t) => {
}) })
}) })
test('return function', (t) => {
return new Promise<void>((resolve) => {
returnJsFunction()((err: Error | undefined, content: string) => {
t.is(err, undefined)
t.is(content, 'hello world')
resolve()
})
})
})
test('object', (t) => { test('object', (t) => {
t.deepEqual(listObjKeys({ name: 'John Doe', age: 20 }), ['name', 'age']) t.deepEqual(listObjKeys({ name: 'John Doe', age: 20 }), ['name', 'age'])
t.deepEqual(createObj(), { test: 1 }) t.deepEqual(createObj(), { test: 1 })

View file

@ -28,6 +28,7 @@ export function optionStartEnd(callback: (arg0: string | undefined | null, arg1:
export function optionOnly(callback: (arg0?: string | undefined | null) => void): void export function optionOnly(callback: (arg0?: string | undefined | null) => void): void
/** napi = { version = 2, features = ["serde-json"] } */ /** napi = { version = 2, features = ["serde-json"] } */
export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void export function readFile(callback: (arg0: Error | undefined, arg1?: string | undefined | null) => void): void
export function returnJsFunction(): (...args: any[]) => any
export function eitherStringOrNumber(input: string | number): number export function eitherStringOrNumber(input: string | number): number
export function returnEither(input: number): string | number export function returnEither(input: number): string | number
export function either3(input: string | number | boolean): number export function either3(input: string | number | boolean): number

View file

@ -1,6 +1,7 @@
use napi::bindgen_prelude::*;
use std::env; use std::env;
use napi::bindgen_prelude::*;
#[napi] #[napi]
fn get_cwd<T: Fn(String) -> Result<()>>(callback: T) { fn get_cwd<T: Fn(String) -> Result<()>>(callback: T) {
callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap(); callback(env::current_dir().unwrap().to_string_lossy().to_string()).unwrap();
@ -40,3 +41,8 @@ fn read_file_content() -> Result<String> {
// serde_json::from_str(&s)?; // serde_json::from_str(&s)?;
Ok("hello world".to_string()) Ok("hello world".to_string())
} }
#[napi]
fn return_js_function() -> Result<JsFunction> {
get_js_function(read_file_js_function)
}