diff --git a/napi/src/env.rs b/napi/src/env.rs index 961f5701..4b601205 100644 --- a/napi/src/env.rs +++ b/napi/src/env.rs @@ -1,6 +1,7 @@ use std::any::TypeId; use std::convert::TryInto; use std::ffi::CString; +use std::mem; use std::os::raw::{c_char, c_void}; use std::ptr; @@ -275,7 +276,7 @@ impl Env { value: raw_value, value_type: ValueType::Object, }), - data, + mem::ManuallyDrop::new(data), )) } @@ -306,10 +307,55 @@ impl Env { value: raw_value, value_type: ValueType::Object, }), - data, + mem::ManuallyDrop::new(data), )) } + #[inline] + /// # Safety + /// Mostly same with `create_buffer_with_data`, but you must ensure data will be dropped **after** the `Buffer` is been GC. + /// + /// And should manually trigger `Env::adjust_external_memory` after data is dropped. + pub unsafe fn create_buffer_with_manually_drop_data(&self, data: &[u8]) -> Result { + let length = data.len(); + let mut raw_value = ptr::null_mut(); + let data_ptr = data.as_ptr(); + check_status!(sys::napi_create_external_buffer( + self.0, + length, + data_ptr as *mut c_void, + None, + ptr::null_mut(), + &mut raw_value, + ))?; + let mut changed = 0; + check_status!(sys::napi_adjust_external_memory( + self.0, + length as i64, + &mut changed + ))?; + Ok(JsBufferValue::new( + JsBuffer(Value { + env: self.0, + value: raw_value, + value_type: ValueType::Object, + }), + mem::ManuallyDrop::new(Vec::from_raw_parts(data_ptr as *mut u8, length, length)), + )) + } + + #[inline] + /// This function gives V8 an indication of the amount of externally allocated memory that is kept alive by JavaScript objects (i.e. a JavaScript object that points to its own memory allocated by a native module). + /// + /// Registering externally allocated memory will trigger global garbage collections more often than it would otherwise. + /// + /// ***ATTENTION ⚠️***, do not use this with `create_buffer_with_data/create_arraybuffer_with_data`, since these two functions already called the `adjust_external_memory` internal. + pub fn adjust_external_memory(&mut self, size: i64) -> Result { + let mut changed = 0i64; + check_status!(unsafe { sys::napi_adjust_external_memory(self.0, size, &mut changed) })?; + Ok(changed) + } + #[inline] /// This API allocates a node::Buffer object and initializes it with data copied from the passed-in buffer. /// While this is still a fully-supported data structure, in most cases using a TypedArray will suffice. @@ -336,7 +382,7 @@ impl Env { value: raw_value, value_type: ValueType::Object, }), - unsafe { Vec::from_raw_parts(copy_data as *mut u8, length, length) }, + mem::ManuallyDrop::new(unsafe { Vec::from_raw_parts(copy_data as *mut u8, length, length) }), )) } diff --git a/napi/src/js_values/buffer.rs b/napi/src/js_values/buffer.rs index 4cf485a8..b3926259 100644 --- a/napi/src/js_values/buffer.rs +++ b/napi/src/js_values/buffer.rs @@ -55,11 +55,8 @@ impl JsBufferValue { } #[inline] - pub fn new(value: JsBuffer, data: Vec) -> Self { - JsBufferValue { - value, - data: mem::ManuallyDrop::new(data), - } + pub fn new(value: JsBuffer, data: mem::ManuallyDrop>) -> Self { + JsBufferValue { value, data } } #[inline] diff --git a/napi/src/js_values/mod.rs b/napi/src/js_values/mod.rs index 326d7dd8..a2a9c76e 100644 --- a/napi/src/js_values/mod.rs +++ b/napi/src/js_values/mod.rs @@ -607,4 +607,15 @@ impl JsUnknown { pub fn get_type(&self) -> Result { unsafe { type_of!(self.0.env, self.0.value) } } + + #[inline] + /// # Safety + /// This function should be called after `JsUnknown::get_type` + /// And the `V` must be match with the return value of `get_type` + pub unsafe fn cast(self) -> V + where + V: NapiValue, + { + V::from_raw_unchecked(self.0.env, self.0.value) + } } diff --git a/napi/src/lib.rs b/napi/src/lib.rs index 9230f83a..88c44844 100644 --- a/napi/src/lib.rs +++ b/napi/src/lib.rs @@ -6,7 +6,7 @@ //! //! ## Feature flags //! -//! ### napi1 ~ napi6 +//! ### napi1 ~ napi7 //! //! Because `NodeJS` N-API has versions. So there are feature flags to choose what version of `N-API` you want to build for. //! For example, if you want build a library which can be used by `node@10.17.0`, you should choose the `napi5` or lower. diff --git a/test_module/__test__/js-value.spec.ts b/test_module/__test__/js-value.spec.ts index 402da262..d5bbf08c 100644 --- a/test_module/__test__/js-value.spec.ts +++ b/test_module/__test__/js-value.spec.ts @@ -47,3 +47,14 @@ test('strict_equals', (t) => { t.false(bindings.strictEquals(NaN, NaN)) t.true(bindings.strictEquals(a, a)) }) + +test('cast_unknown', (t) => { + const f = {} + const r = bindings.castUnknown(f) + t.is(f, r) +}) + +test('cast_unknown will not throw', (t) => { + const f = 1 + t.notThrows(() => bindings.castUnknown(f)) +}) diff --git a/test_module/src/env.rs b/test_module/src/env.rs index 0648a4f8..43dda012 100644 --- a/test_module/src/env.rs +++ b/test_module/src/env.rs @@ -26,10 +26,17 @@ pub fn strict_equals(ctx: CallContext) -> Result { ctx.env.get_boolean(ctx.env.strict_equals(a, b)?) } +#[js_function(1)] +pub fn cast_unknown(ctx: CallContext) -> Result { + let arg: JsUnknown = ctx.get(0)?; + Ok(unsafe { arg.cast::() }) +} + pub fn register_js(exports: &mut JsObject) -> Result<()> { exports.create_named_method("instanceof", instanceof)?; exports.create_named_method("isTypedarray", is_typedarray)?; exports.create_named_method("isDataview", is_dataview)?; exports.create_named_method("strictEquals", strict_equals)?; + exports.create_named_method("castUnknown", cast_unknown)?; Ok(()) } diff --git a/test_module/tsconfig.json b/test_module/tsconfig.json index d1d19d67..c5cf16e7 100644 --- a/test_module/tsconfig.json +++ b/test_module/tsconfig.json @@ -2,7 +2,8 @@ "extends": "../tsconfig.json", "include": ["."], "compilerOptions": { - "outDir": "./dist" + "outDir": "./dist", + "rootDir": "__test__" }, "exclude": ["dist"] }