From f639628ce8297a64aa72bc14ff00aaf5fcc2a147 Mon Sep 17 00:00:00 2001 From: yuuang Date: Mon, 20 May 2024 17:54:54 +0800 Subject: [PATCH] fix: add freePointer, free memory after ffi-call prevent memory leak (#40) --- README.md | 132 ++++---- cpp/sum.cpp | 13 +- memory.js | 5 + scripts/build.js | 9 +- scripts/type.js | 411 +++++++++++++++++++++--- scripts/types.d.ts | 9 + src/datatype/object_calculate.rs | 271 +++++++++------- src/datatype/pointer.rs | 388 ++++++++++++++++++++++- src/define.rs | 60 +++- src/lib.rs | 108 +++++-- src/utils/dataprocess.rs | 516 ++++++++++++++++--------------- test.ts | 218 ++++++------- 12 files changed, 1526 insertions(+), 614 deletions(-) diff --git a/README.md b/README.md index ba4d186..e0f3a76 100644 --- a/README.md +++ b/README.md @@ -354,7 +354,7 @@ deepStrictEqual(stringArr, load({ In `ffi-rs`, we use [DataType.External](https://nodejs.org/api/n-api.html#napi_create_external) for wrapping the `pointer` which enables it to be passed between `Node.js` and `C`. -`Pointer` is complicated and underlying, `ffi-rs` provide four functions to handle this pointer include `createPointer`, `restorePointer`, `wrapPointer`, `unwrapPointer` for different scene. +`Pointer` is complicated and underlying, `ffi-rs` provide four functions to handle this pointer include `createPointer`, `restorePointer`, `unwrapPointer`, `wrapPointer`, `freePointer` for different scene. ```cpp extern "C" const char *concatenateStrings(const char *str1, const char *str2) { @@ -454,6 +454,20 @@ const restoreData = restorePointer({ deepStrictEqual(restoreData, [[1.1, 2.2]]) ``` +#### freePointer + +`freePointer` is used to free memory which are not be freed automatically. + +At default, `ffi-rs` will free data memory for ffi call args and return result prevent memory leak.Except in the following cases. + +- set `freeResultMemory: false` when call `load` method + +If you set freeResultMemory to false, `ffi-rs` will not release the return result memory which was malloc in c environment + +- Use `DataType.External` as paramsType or retType + +If developers use `DataType.External` as paramsType or retType, please use `freePointer` to release the memory of pointer. ref [test.ts](./test.ts#170) + #### wrapPointer `wrapPointer` is used to create multiple pointer. @@ -473,7 +487,6 @@ const ptr = load({ // wrapPtr type is *mut *mut c_char const wrapPtr = wrapPointer([ptr])[0] -``` #### unwrapPointer @@ -530,7 +543,7 @@ staticBytes: arrayConstructor({ ## Function -`ffi-rs` supports passing js function to c, like this +`ffi-rs` supports passing js function pointer to c function, like this. ```cpp typedef const void (*FunctionPointer)(int a, bool b, char *c, double d, @@ -564,64 +577,62 @@ extern "C" void callFunction(FunctionPointer func) { Corresponds to the code above,you can use `ffi-rs` like ```js -let count = 0; -const func = (a, b, c, d, e, f, g) => { - equal(a, 100); - equal(b, false); - equal(c, "Hello, World!"); - equal(d, "100.11"); - deepStrictEqual(e, ["Hello", "world"]); - deepStrictEqual(f, [101, 202, 303]); - deepStrictEqual(g, person); - console.log("callback called"); - count++; - if (count === 4) { - logGreen("test succeed"); - process.exit(0); - } -}; -const funcExternal = createPointer({ - paramsType: [funcConstructor({ - paramsType: [ - DataType.I32, - DataType.Boolean, - DataType.String, - DataType.Double, - arrayConstructor({ type: DataType.StringArray, length: 2 }), - arrayConstructor({ type: DataType.I32Array, length: 3 }), - personType, - ], +const testFunction = () => { + const func = (a, b, c, d, e, f, g) => { + equal(a, 100); + equal(b, false); + equal(c, "Hello, World!"); + equal(d, "100.11"); + deepStrictEqual(e, ["Hello", "world"]); + deepStrictEqual(f, [101, 202, 303]); + deepStrictEqual(g, person); + logGreen("test function succeed"); + // free function memory when it not in use + freePointer({ + paramsType: [funcConstructor({ + paramsType: [ + DataType.I32, + DataType.Boolean, + DataType.String, + DataType.Double, + arrayConstructor({ type: DataType.StringArray, length: 2 }), + arrayConstructor({ type: DataType.I32Array, length: 3 }), + personType, + ], + retType: DataType.Void, + })], + paramsValue: funcExternal + }) + if (!process.env.MEMORY) { + close("libsum"); + } + }; + // suggest use createPointer to create a function pointer for manual memory management + const funcExternal = createPointer({ + paramsType: [funcConstructor({ + paramsType: [ + DataType.I32, + DataType.Boolean, + DataType.String, + DataType.Double, + arrayConstructor({ type: DataType.StringArray, length: 2 }), + arrayConstructor({ type: DataType.I32Array, length: 3 }), + personType, + ], + retType: DataType.Void, + })], + paramsValue: [func] + }) + load({ + library: "libsum", + funcName: "callFunction", retType: DataType.Void, - })], - paramsValue: [func] -}) -load({ - library: "libsum", - funcName: "callFunction", - retType: DataType.Void, - paramsType: [funcConstructor({ paramsType: [ - DataType.I32, - DataType.Boolean, - DataType.String, - DataType.Double, - arrayConstructor({ type: DataType.StringArray, length: 2 }), - arrayConstructor({ type: DataType.I32Array, length: 3 }), - personType, + DataType.External, ], - retType: DataType.Void, - })], - paramsValue: [func] -}); -load({ - library: "libsum", - funcName: "callFunction", - retType: DataType.Void, - paramsType: [ - DataType.External, - ], - paramsValue: unwrapPointer(funcExternal), -}); + paramsValue: unwrapPointer(funcExternal), + }); +} ``` The function parameters supports type are all in the example above @@ -673,6 +684,11 @@ load({ ], paramsValue: [classPointer], }) +freePointer({ + paramsType: [DataType.External], + paramsValue: [classPointer], + pointerType: PointerType.CPointer +}) ``` ## errno diff --git a/cpp/sum.cpp b/cpp/sum.cpp index 86944f1..a110fa1 100644 --- a/cpp/sum.cpp +++ b/cpp/sum.cpp @@ -48,13 +48,16 @@ extern "C" double *createArrayFloat(const float *arr, int size) { } extern "C" char **createArrayString(char **arr, int size) { - char **vec = (char **)malloc((size) * sizeof(char *)); + char **vec = (char **)malloc(size * sizeof(char *)); + if (vec == NULL) { + return NULL; + } + for (int i = 0; i < size; i++) { - vec[i] = arr[i]; + vec[i] = strdup(arr[i]); } return vec; } - extern "C" bool return_opposite(bool input) { return !input; } typedef struct Person { @@ -150,9 +153,6 @@ typedef const void (*FunctionPointer)(int a, bool b, char *c, double d, char **e, int *f, Person *g); extern "C" void callFunction(FunctionPointer func) { - printf("callFunction\n"); - - for (int i = 0; i < 2; i++) { int a = 100; bool b = false; double d = 100.11; @@ -170,7 +170,6 @@ extern "C" void callFunction(FunctionPointer func) { Person *p = createPerson(); func(a, b, c, d, stringArray, i32Array, p); - } } // 定义 C++ 类 diff --git a/memory.js b/memory.js index 32cfe2d..891dfd8 100644 --- a/memory.js +++ b/memory.js @@ -1,9 +1,14 @@ const Koa = require('koa'); const app = new Koa(); process.env.MEMORY = 1 +process.env.SILENT = 1 const { unitTest } = require('./test') app.use(async ctx => { + if (ctx.req.url === '/gc') { + console.log('gc') + global.gc() + } console.log('memory:', (process.memoryUsage().rss / 1024 / 1024).toFixed(2)) unitTest() ctx.body = 'success' diff --git a/scripts/build.js b/scripts/build.js index 958b07d..fb81c73 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,11 +1,16 @@ const { execSync } = require("child_process"); +const { cp } = require('shelljs') +const { resolve } = require('path') + +const cwd = process.cwd() const options = { stdio: "inherit", }; const target = process.env.target; execSync( - `yarn build:c && napi build --platform --release --js-package-name @yuuang/ffi-rs ${target ? `--target ${target}` : "" - }&& node scripts/type.js`, + `yarn build:c && napi build --platform --release --js-package-name @yuuang/ffi-rs ${target ? `--target ${target}` : ""}`, options, ); +cp(resolve(cwd, './scripts/type.js'), resolve(cwd, './index.js')) +cp(resolve(cwd, './scripts/types.d.ts'), resolve(cwd, './index.d.ts')) diff --git a/scripts/type.js b/scripts/type.js index ac802d0..efbdcbc 100644 --- a/scripts/type.js +++ b/scripts/type.js @@ -1,39 +1,374 @@ -const { readFile, writeFile } = require("fs/promises"); -const { resolve } = require("path"); - -(async () => { - const entryContent = (await readFile(resolve(process.cwd(), "./index.js"))) - .toString() - .replace("paramsType: Array", "paramsType: Array") - .replace("retType: unknown", "retType: DataFieldType"); - await writeFile( - resolve(process.cwd(), "./index.js"), - ` - ${entryContent} - exports.arrayConstructor = (options) => ({ - dynamicArray: true, - ...options, - ffiTypeTag: 'array' - }) - exports.funcConstructor = (options) => ({ - ffiTypeTag: 'function', - ...options, - }) - exports.define = (obj) => { - const res = {} - Object.entries(obj).map(([funcName, funcDesc]) => { - res[funcName] = (paramsValue) => load({ - ...obj[funcName], - funcName, - paramsValue - }) +const { existsSync, readFileSync } = require('fs') +const { join } = require('path') + +const { platform, arch } = process + +let nativeBinding = null +let localFileExisted = false +let loadError = null + +function isMusl() { + // For Node 10 + if (!process.report || typeof process.report.getReport !== 'function') { + try { + const lddPath = require('child_process').execSync('which ldd').toString().trim() + return readFileSync(lddPath, 'utf8').includes('musl') + } catch (e) { + return true + } + } else { + const { glibcVersionRuntime } = process.report.getReport().header + return !glibcVersionRuntime + } +} + +switch (platform) { + case 'android': + switch (arch) { + case 'arm64': + localFileExisted = existsSync(join(__dirname, 'ffi-rs.android-arm64.node')) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.android-arm64.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-android-arm64') + } + } catch (e) { + loadError = e + } + break + case 'arm': + localFileExisted = existsSync(join(__dirname, 'ffi-rs.android-arm-eabi.node')) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.android-arm-eabi.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-android-arm-eabi') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Android ${arch}`) + } + break + case 'win32': + switch (arch) { + case 'x64': + localFileExisted = existsSync( + join(__dirname, 'ffi-rs.win32-x64-msvc.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.win32-x64-msvc.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-win32-x64-msvc') + } + } catch (e) { + loadError = e + } + break + case 'ia32': + localFileExisted = existsSync( + join(__dirname, 'ffi-rs.win32-ia32-msvc.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.win32-ia32-msvc.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-win32-ia32-msvc') + } + } catch (e) { + loadError = e + } + break + case 'arm64': + localFileExisted = existsSync( + join(__dirname, 'ffi-rs.win32-arm64-msvc.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.win32-arm64-msvc.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-win32-arm64-msvc') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Windows: ${arch}`) + } + break + case 'darwin': + localFileExisted = existsSync(join(__dirname, 'ffi-rs.darwin-universal.node')) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.darwin-universal.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-darwin-universal') + } + break + } catch { } + switch (arch) { + case 'x64': + localFileExisted = existsSync(join(__dirname, 'ffi-rs.darwin-x64.node')) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.darwin-x64.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-darwin-x64') + } + } catch (e) { + loadError = e + } + break + case 'arm64': + localFileExisted = existsSync( + join(__dirname, 'ffi-rs.darwin-arm64.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.darwin-arm64.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-darwin-arm64') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on macOS: ${arch}`) + } + break + case 'freebsd': + if (arch !== 'x64') { + throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) + } + localFileExisted = existsSync(join(__dirname, 'ffi-rs.freebsd-x64.node')) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.freebsd-x64.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-freebsd-x64') + } + } catch (e) { + loadError = e + } + break + case 'linux': + switch (arch) { + case 'x64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'ffi-rs.linux-x64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.linux-x64-musl.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-linux-x64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'ffi-rs.linux-x64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.linux-x64-gnu.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-linux-x64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 'arm64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'ffi-rs.linux-arm64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.linux-arm64-musl.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-linux-arm64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'ffi-rs.linux-arm64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.linux-arm64-gnu.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-linux-arm64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 'arm': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'ffi-rs.linux-arm-musleabihf.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.linux-arm-musleabihf.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-linux-arm-musleabihf') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'ffi-rs.linux-arm-gnueabihf.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.linux-arm-gnueabihf.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-linux-arm-gnueabihf') + } + } catch (e) { + loadError = e + } + } + break + case 'riscv64': + if (isMusl()) { + localFileExisted = existsSync( + join(__dirname, 'ffi-rs.linux-riscv64-musl.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.linux-riscv64-musl.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-linux-riscv64-musl') + } + } catch (e) { + loadError = e + } + } else { + localFileExisted = existsSync( + join(__dirname, 'ffi-rs.linux-riscv64-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.linux-riscv64-gnu.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-linux-riscv64-gnu') + } + } catch (e) { + loadError = e + } + } + break + case 's390x': + localFileExisted = existsSync( + join(__dirname, 'ffi-rs.linux-s390x-gnu.node') + ) + try { + if (localFileExisted) { + nativeBinding = require('./ffi-rs.linux-s390x-gnu.node') + } else { + nativeBinding = require('@yuuang/ffi-rs-linux-s390x-gnu') + } + } catch (e) { + loadError = e + } + break + default: + throw new Error(`Unsupported architecture on Linux: ${arch}`) + } + break + default: + throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) +} + +if (!nativeBinding) { + if (loadError) { + throw loadError + } + throw new Error(`Failed to load native binding`) + (processParamsTypeForArray(params)) +} + +const { DataType, createPointer, restorePointer, unwrapPointer, wrapPointer, freePointer, open, close, load } = nativeBinding + +module.exports.DataType = DataType +module.exports.PointerType = nativeBinding.PointerType +module.exports.open = open +module.exports.close = close +module.exports.load = load + +const arrayDataType = [DataType.I32Array, DataType.StringArray, DataType.DoubleArray, DataType.U8Array, DataType.FloatArray] +const arrayConstructor = (options) => ({ + dynamicArray: true, + ...options, + ffiTypeTag: 'array' +}) + +const processParamsTypeForArray = (params) => { + params.paramsType = params.paramsType?.map((paramType, index) => { + if (arrayDataType.includes(paramType)) { + return arrayConstructor({ + type: paramType, + length: params.paramsValue[index].length, }) - return res - } - `, - ); - const typesContent = ( - await readFile(resolve(process.cwd(), "./scripts/types.d.ts")) - ).toString(); - await writeFile(resolve(process.cwd(), "./index.d.ts"), typesContent); -})(); + } + return paramType + }) + return params +} + +const setFreePointerTag = (params) => { + params.paramsType = params.paramsType?.map((paramType, index) => { + if (paramType.ffiTypeTag === 'function') { + paramType.needFree = true + } + return paramType + }) + return params +} + +const wrapLoad = (params) => { + if (params.freeResultMemory === undefined) { + params.freeResultMemory = true + } + return load(processParamsTypeForArray(params)) +} +exports.load = wrapLoad +exports.createPointer = (params) => createPointer(processParamsTypeForArray(params)) +exports.restorePointer = (params) => restorePointer(processParamsTypeForArray(params)) +exports.unwrapPointer = (params) => unwrapPointer(processParamsTypeForArray(params)) +exports.wrapPointer = (params) => wrapPointer(processParamsTypeForArray(params)) +exports.freePointer = (params) => freePointer(setFreePointerTag(processParamsTypeForArray(params))) +exports.arrayConstructor = arrayConstructor +exports.funcConstructor = (options) => ({ + ffiTypeTag: 'function', + needFree: false, + ...options, +}) +exports.define = (obj) => { + const res = {} + Object.entries(obj).map(([funcName, funcDesc]) => { + res[funcName] = (paramsValue) => wrapLoad({ + ...obj[funcName], + funcName, + paramsValue + }) + }) + return res +} diff --git a/scripts/types.d.ts b/scripts/types.d.ts index 082d296..c631aa9 100644 --- a/scripts/types.d.ts +++ b/scripts/types.d.ts @@ -79,6 +79,7 @@ export type ArrayConstructorOptions = { export type FuncConstructorOptions = { paramsType: Array; retType: FieldType; + needFree?: boolean }; export function arrayConstructor( @@ -102,8 +103,15 @@ export function createPointer(params: { paramsValue: Array; }): JsExternal[] +export enum PointerType { + RsPointer = 0, + CPointer = 1 +} + export function freePointer(params: { + paramsType: Array; paramsValue: Array; + pointerType: PointerType }): void @@ -151,6 +159,7 @@ export type FFIParams( params: FFIParams, diff --git a/src/datatype/object_calculate.rs b/src/datatype/object_calculate.rs index 8ff076d..2bcdca7 100644 --- a/src/datatype/object_calculate.rs +++ b/src/datatype/object_calculate.rs @@ -1,5 +1,7 @@ use crate::define::*; -use crate::utils::dataprocess::{get_array_desc, get_ffi_tag, get_js_external_wrap_data}; +use crate::utils::dataprocess::{ + get_array_desc, get_array_value, get_ffi_tag, get_js_external_wrap_data, +}; use crate::utils::object_utils::get_size_align; use crate::RefDataType; use indexmap::IndexMap; @@ -51,23 +53,28 @@ pub fn calculate_struct_size(map: &IndexMap) -> (usize, usi let FFIARRARYDESC { array_type, array_len, + dynamic_array, .. } = array_desc; - let (mut type_size, type_align) = match array_type { - RefDataType::U8Array => get_size_align::(), - RefDataType::I32Array => get_size_align::(), - RefDataType::DoubleArray => get_size_align::(), - _ => panic!( - "write {:?} static array in struct is unsupported", - array_type - ), - }; - type_size = type_size * array_len; - let align = align.max(type_align); - let padding = (type_align - (offset % type_align)) % type_align; - let size = size + padding + type_size; - let offset = offset + padding + type_size; - (size, align, offset) + if !dynamic_array { + let (mut type_size, type_align) = match array_type { + RefDataType::U8Array => get_size_align::(), + RefDataType::I32Array => get_size_align::(), + RefDataType::DoubleArray => get_size_align::(), + _ => panic!( + "write {:?} to static array in struct is unsupported", + array_type + ), + }; + type_size = type_size * array_len; + let align = align.max(type_align); + let padding = (type_align - (offset % type_align)) % type_align; + let size = size + padding + type_size; + let offset = offset + padding + type_size; + (size, align, offset) + } else { + calculate_pointer(size, align, offset) + } } else { calculate_pointer(size, align, offset) } @@ -173,116 +180,158 @@ pub unsafe fn generate_c_struct( offset += size + padding; size } - RsArgsValue::StringArray(str_arr) => { - let (size, align) = get_size_align::<*mut c_void>(); - let padding = (align - (offset % align)) % align; - field_ptr = field_ptr.offset(padding as isize); - let c_char_vec: Vec<*const c_char> = str_arr - .into_iter() - .map(|str| { - let c_string = string_to_c_string(str); - let ptr = c_string.as_ptr(); - std::mem::forget(c_string); - ptr - }) - .collect(); - (field_ptr as *mut *const *const c_char).write(c_char_vec.as_ptr()); - std::mem::forget(c_char_vec); - offset += size + padding; - size - } - RsArgsValue::DoubleArray(arr) => { - let (size, align) = get_size_align::<*mut c_void>(); - let padding = (align - (offset % align)) % align; - field_ptr = field_ptr.offset(padding as isize); - (field_ptr as *mut *const c_double).write(arr.as_ptr()); - std::mem::forget(arr); - offset += size + padding; - size - } - RsArgsValue::FloatArray(arr) => { - let (size, align) = get_size_align::<*mut c_void>(); - let padding = (align - (offset % align)) % align; - field_ptr = field_ptr.offset(padding as isize); - (field_ptr as *mut *const c_float).write(arr.as_ptr()); - std::mem::forget(arr); - offset += size + padding; - size - } - RsArgsValue::I32Array(arr) => { + RsArgsValue::External(val) => { let (size, align) = get_size_align::<*mut c_void>(); let padding = (align - (offset % align)) % align; field_ptr = field_ptr.offset(padding as isize); - (field_ptr as *mut *const c_int).write(arr.as_ptr()); - std::mem::forget(arr); + (field_ptr as *mut *const c_void).write(get_js_external_wrap_data(&env, val)?); offset += size + padding; size } - RsArgsValue::U8Array(buffer, _) => { - let buffer = buffer.unwrap(); - let (size, align) = get_size_align::<*mut c_void>(); + RsArgsValue::Void(_) => { + let (size, align) = get_size_align::<()>(); let padding = (align - (offset % align)) % align; field_ptr = field_ptr.offset(padding as isize); - (field_ptr as *mut *const c_uchar).write(buffer.as_ptr()); - std::mem::forget(buffer); + (field_ptr as *mut ()).write(()); offset += size + padding; size } - RsArgsValue::Object(val) => { + RsArgsValue::Object(mut val) => { if let FFITag::Array = get_ffi_tag(&val) { let array_desc = get_array_desc(&val); - // write static array data to struct + let array_value = get_array_value(&mut val).unwrap(); let FFIARRARYDESC { array_type, array_len, - array_value, dynamic_array, .. } = array_desc; - if dynamic_array { - panic!("generate struct field unsupport use object describe array") - } - match array_type { + let field_size = match array_type { RefDataType::U8Array => { - let (size, align) = get_size_align::(); - let field_size = size * array_len; - let padding = (align - (offset % align)) % align; - field_ptr = field_ptr.offset(padding as isize); - if let RsArgsValue::U8Array(buffer, _) = array_value.unwrap() { - let buffer = buffer.as_ref().unwrap(); - std::ptr::copy(buffer.as_ptr(), field_ptr as *mut u8, array_len); - offset += field_size + padding; + if let RsArgsValue::U8Array(buffer, _) = array_value { + let buffer = buffer.unwrap(); + if !dynamic_array { + let (size, align) = get_size_align::(); + let field_size = size * array_len; + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + std::ptr::copy(buffer.as_ptr(), field_ptr as *mut u8, array_len); + offset += field_size + padding; + field_size + } else { + let (size, align) = get_size_align::<*mut c_void>(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + (field_ptr as *mut *const c_uchar).write(buffer.as_ptr()); + offset += size + padding; + size + } + } else { + return Err(FFIError::Panic(format!("error array type {:?}", array_type)).into()); } - field_size } RefDataType::I32Array => { - let (size, align) = get_size_align::(); - let field_size = size * array_len; - let padding = (align - (offset % align)) % align; - field_ptr = field_ptr.offset(padding as isize); - if let RsArgsValue::I32Array(arr) = array_value.unwrap() { - std::ptr::copy(arr.as_ptr(), field_ptr as *mut i32, array_len); - offset += field_size + padding; + if let RsArgsValue::I32Array(arr) = array_value { + if !dynamic_array { + let (size, align) = get_size_align::(); + let field_size = size * array_len; + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + std::ptr::copy(arr.as_ptr(), field_ptr as *mut i32, array_len); + offset += field_size + padding; + field_size + } else { + let (size, align) = get_size_align::<*mut c_void>(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + (field_ptr as *mut *const c_int).write(arr.as_ptr()); + std::mem::forget(arr); + offset += size + padding; + size + } + } else { + return Err(FFIError::Panic(format!("error array type {:?}", array_type)).into()); } - field_size } RefDataType::DoubleArray => { - let (size, align) = get_size_align::(); - let field_size = size * array_len; - let padding = (align - (offset % align)) % align; - field_ptr = field_ptr.offset(padding as isize); - if let RsArgsValue::DoubleArray(arr) = array_value.unwrap() { - std::ptr::copy(arr.as_ptr(), field_ptr as *mut f64, array_len); - offset += field_size + padding; + if let RsArgsValue::DoubleArray(arr) = array_value { + if !dynamic_array { + let (size, align) = get_size_align::(); + let field_size = size * array_len; + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + std::ptr::copy(arr.as_ptr(), field_ptr as *mut f64, array_len); + offset += field_size + padding; + field_size + } else { + let (size, align) = get_size_align::<*mut c_void>(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + (field_ptr as *mut *const c_double).write(arr.as_ptr()); + std::mem::forget(arr); + offset += size + padding; + size + } + } else { + return Err(FFIError::Panic(format!("error array type {:?}", array_type)).into()); } - field_size } - _ => panic!( - "write {:?} static array in struct is unsupported", - array_type - ), - } + RefDataType::FloatArray => { + if let RsArgsValue::FloatArray(arr) = array_value { + if !dynamic_array { + let (size, align) = get_size_align::(); + let field_size = size * array_len; + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + std::ptr::copy(arr.as_ptr(), field_ptr as *mut f32, array_len); + offset += field_size + padding; + field_size + } else { + let (size, align) = get_size_align::<*mut c_void>(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + (field_ptr as *mut *const c_float).write(arr.as_ptr()); + std::mem::forget(arr); + offset += size + padding; + size + } + } else { + return Err(FFIError::Panic(format!("error array type {:?}", array_type)).into()); + } + } + RefDataType::StringArray => { + if let RsArgsValue::StringArray(arr) = array_value { + if !dynamic_array { + panic!( + "write {:?} to static array in struct is unsupported", + array_type + ) + } else { + let (size, align) = get_size_align::<*mut c_void>(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + let c_char_vec: Vec<*const c_char> = arr + .into_iter() + .map(|str| { + let c_string = string_to_c_string(str); + let ptr = c_string.as_ptr(); + std::mem::forget(c_string); + ptr + }) + .collect(); + (field_ptr as *mut *const *const c_char).write(c_char_vec.as_ptr()); + std::mem::forget(c_char_vec); + offset += size + padding; + size + } + } else { + return Err(FFIError::Panic(format!("error array type {:?}", array_type)).into()); + } + } + }; + field_size } else { + // raw object or function let (size, align) = get_size_align::<*mut c_void>(); let padding = (align - (offset % align)) % align; field_ptr = field_ptr.offset(padding as isize); @@ -292,23 +341,19 @@ pub unsafe fn generate_c_struct( size } } - RsArgsValue::External(val) => { - let (size, align) = get_size_align::<*mut c_void>(); - let padding = (align - (offset % align)) % align; - field_ptr = field_ptr.offset(padding as isize); - (field_ptr as *mut *const c_void).write(get_js_external_wrap_data(&env, val)?); - offset += size + padding; - size - } - RsArgsValue::Void(_) => { - let (size, align) = get_size_align::<()>(); - let padding = (align - (offset % align)) % align; - field_ptr = field_ptr.offset(padding as isize); - (field_ptr as *mut ()).write(()); - offset += size + padding; - size - } RsArgsValue::Function(_, _) => panic!("write_data error {:?}", field_val), + RsArgsValue::StringArray(_) + | RsArgsValue::FloatArray(_) + | RsArgsValue::I32Array(_) + | RsArgsValue::DoubleArray(_) + | RsArgsValue::U8Array(_, _) => { + return Err( + FFIError::Panic(format!( + "In the latest ffi-rs version, please use ffi-rs.arrayConstrutor to describe array type" + )) + .into(), + ) + } }; field_ptr = field_ptr.offset(field_size as isize); } diff --git a/src/datatype/pointer.rs b/src/datatype/pointer.rs index c4208e4..fb44430 100644 --- a/src/datatype/pointer.rs +++ b/src/datatype/pointer.rs @@ -1,5 +1,11 @@ -use std::ffi::{c_char, CString}; +use crate::utils::dataprocess::{get_array_desc, get_ffi_tag, get_func_desc}; +use crate::utils::object_utils::get_size_align; +use indexmap::IndexMap; +use libc::{c_double, c_float, c_int, c_void, free}; +use libffi::middle::Closure; +use std::ffi::{c_char, c_longlong, c_uchar, c_ulonglong, CStr, CString}; +use crate::define::*; pub trait ArrayPointer { type Output; unsafe fn get_and_advance(&mut self) -> Self::Output; @@ -27,7 +33,7 @@ impl ArrayPointer for *mut *mut c_char { unsafe fn get_and_advance(&mut self) -> Self::Output { let value = **self; *self = self.offset(1); - CString::from_raw(value).into_string().unwrap() + CStr::from_ptr(value).to_string_lossy().to_string() } } pub fn create_array_from_pointer

(mut pointer: P, len: usize) -> Vec @@ -36,3 +42,381 @@ where { unsafe { (0..len).map(|_| pointer.get_and_advance()).collect() } } + +unsafe fn free_struct_memory( + ptr: *mut c_void, + struct_desc: IndexMap, + ptr_type: PointerType, +) { + let mut field_ptr = ptr; + let mut offset = 0; + let mut field_size = 0; + for (_, val) in struct_desc { + if let RsArgsValue::I32(number) = val { + let data_type = number_to_basic_data_type(number); + match data_type { + BasicDataType::U8 => { + let (size, align) = get_size_align::(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + offset += size + padding; + field_size = size + } + BasicDataType::I32 => { + let (size, align) = get_size_align::(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + offset += size + padding; + field_size = size + } + BasicDataType::I64 => { + let (size, align) = get_size_align::(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + offset += size + padding; + field_size = size + } + BasicDataType::U64 => { + let (size, align) = get_size_align::(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + offset += size + padding; + field_size = size + } + BasicDataType::Float => { + let (size, align) = get_size_align::(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + offset += size + padding; + field_size = size + } + BasicDataType::Double => { + let (size, align) = get_size_align::(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + offset += size + padding; + field_size = size + } + BasicDataType::Boolean => { + let (size, align) = get_size_align::(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + offset += size + padding; + field_size = size + } + BasicDataType::Void => { + let (size, align) = (std::mem::size_of::<()>(), std::mem::align_of::<()>()); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + offset += size + padding; + field_size = size + } + BasicDataType::String => { + let (size, align) = get_size_align::<*const c_void>(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + let type_field_ptr = field_ptr as *mut *mut c_char; + match ptr_type { + PointerType::CPointer => free((*type_field_ptr) as *mut c_void), + PointerType::RsPointer => { + let _ = CString::from_raw(*(type_field_ptr as *mut *mut c_char)); + } + } + offset += size + padding; + field_size = size + } + BasicDataType::External => { + let (size, align) = get_size_align::<*const c_void>(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + offset += size + padding; + field_size = size + } + }; + } + if let RsArgsValue::Object(obj) = val { + if let FFITag::Array = get_ffi_tag(&obj) { + let array_desc = get_array_desc(&obj); + // array + let FFIARRARYDESC { + array_type, + array_len, + dynamic_array, + .. + } = array_desc; + match array_type { + RefDataType::StringArray => { + let (size, align) = get_size_align::<*const c_void>(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + if dynamic_array { + free_dynamic_string_array(field_ptr, array_len); + } else { + // + } + offset += size + padding; + field_size = size + } + RefDataType::DoubleArray => { + let (size, align) = if dynamic_array { + get_size_align::<*const c_void>() + } else { + let (size, align) = get_size_align::(); + (size * array_len, align) + }; + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + if dynamic_array { + free_dynamic_double_array(field_ptr, array_len); + } else { + // + } + offset += size + padding; + field_size = size + } + RefDataType::FloatArray => { + let (size, align) = if dynamic_array { + get_size_align::<*const c_void>() + } else { + let (size, align) = get_size_align::(); + (size * array_len, align) + }; + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + if dynamic_array { + free_dynamic_float_array(field_ptr, array_len); + } else { + // + } + offset += size + padding; + field_size = size + } + RefDataType::I32Array => { + let (size, align) = if dynamic_array { + get_size_align::<*const c_void>() + } else { + let (size, align) = get_size_align::(); + (size * array_len, align) + }; + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + if dynamic_array { + free_dynamic_i32_array(field_ptr, array_len) + } else { + // + } + offset += size + padding; + field_size = size + } + RefDataType::U8Array => { + let (size, align) = if dynamic_array { + get_size_align::<*const c_void>() + } else { + let (size, align) = get_size_align::(); + (size * array_len, align) + }; + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + if dynamic_array { + if let PointerType::CPointer = ptr_type { + // only free u8 pointer data when the pointer is allocated in c + // rust u8 pointer memory is buffer + free_dynamic_u8_array(field_ptr, array_len) + } + } else { + // + } + offset += size + padding; + field_size = size + } + }; + } else if let FFITag::Function = get_ffi_tag(&obj) { + let func_desc = get_func_desc(&obj); + if func_desc.need_free { + let _ = free(*(ptr as *mut *mut Closure) as *mut c_void); + } + } else { + // struct + let (size, align) = get_size_align::<*const c_void>(); + let padding = (align - (offset % align)) % align; + field_ptr = field_ptr.offset(padding as isize); + free_struct_memory(*(field_ptr as *mut *mut c_void), obj, ptr_type); + offset += size + padding; + field_size = size + }; + }; + field_ptr = field_ptr.offset(field_size as isize) as *mut c_void; + } +} +pub unsafe fn free_rs_pointer_memory( + ptr: *mut c_void, + ptr_desc: RsArgsValue, + need_free_external: bool, +) { + match ptr_desc { + RsArgsValue::I32(number) => { + let basic_data_type = number_to_basic_data_type(number); + match basic_data_type { + BasicDataType::String => { + let _ = CString::from_raw(*(ptr as *mut *mut c_char)); + } + BasicDataType::U8 => { + let _ = Box::from_raw(ptr); + } + BasicDataType::I32 => { + let _ = Box::from_raw(ptr); + } + BasicDataType::I64 => { + let _ = Box::from_raw(ptr); + } + BasicDataType::U64 => { + let _ = Box::from_raw(ptr); + } + BasicDataType::Void => { + let _ = Box::from_raw(ptr); + } + BasicDataType::Float => { + let _ = Box::from_raw(ptr); + } + BasicDataType::Double => { + let _ = Box::from_raw(ptr); + } + BasicDataType::Boolean => { + let _ = Box::from_raw(ptr); + } + BasicDataType::External => { + if need_free_external { + let _ = Box::from_raw(ptr); + } + } + } + } + RsArgsValue::Object(obj) => { + let ffi_tag = get_ffi_tag(&obj); + if let FFITag::Array = ffi_tag { + let array_desc = get_array_desc(&obj); + // array + let FFIARRARYDESC { + array_type, + array_len, + .. + } = array_desc; + match array_type { + RefDataType::U8Array => free_dynamic_u8_array(ptr, array_len), + RefDataType::I32Array => free_dynamic_i32_array(ptr, array_len), + RefDataType::DoubleArray => free_dynamic_double_array(ptr, array_len), + RefDataType::FloatArray => free_dynamic_float_array(ptr, array_len), + RefDataType::StringArray => free_dynamic_string_array(ptr, array_len), + } + } else if let FFITag::Function = ffi_tag { + let func_desc = get_func_desc(&obj); + if func_desc.need_free { + CLOSURE_MAP + .as_ref() + .unwrap() + .get(&ptr) + .unwrap() + .iter() + .enumerate() + .for_each(|(index, p)| { + if index == 0 { + use napi::threadsafe_function::{ErrorStrategy, ThreadsafeFunction}; + let _ = Box::from_raw( + (*p) as *mut ThreadsafeFunction, ErrorStrategy::Fatal>, + ) + .abort(); + } else { + let _ = Box::from_raw(*p); + } + }); + } + } else { + use super::object_calculate::calculate_struct_size; + use std::alloc::{dealloc, Layout}; + let (size, align) = calculate_struct_size(&obj); + let layout = if size > 0 { + Layout::from_size_align(size, align).unwrap() + } else { + Layout::new::() + }; + free_struct_memory(*(ptr as *mut *mut c_void), obj, PointerType::RsPointer); + dealloc(*(ptr as *mut *mut u8), layout); + } + } + _ => panic!("free rust pointer memory error"), + } +} + +pub unsafe fn free_c_pointer_memory( + ptr: *mut c_void, + ptr_desc: RsArgsValue, + need_free_external: bool, +) { + match ptr_desc { + RsArgsValue::I32(number) => { + let basic_data_type = number_to_basic_data_type(number); + match basic_data_type { + BasicDataType::String => { + free(*(ptr as *mut *mut i8) as *mut c_void); + } + + BasicDataType::External => { + if need_free_external { + free(ptr); + } + } + _ => { + // + } + } + } + RsArgsValue::Object(obj) => { + let ffi_tag = get_ffi_tag(&obj); + if let FFITag::Array = ffi_tag { + let array_desc = get_array_desc(&obj); + // array + let FFIARRARYDESC { + array_type, + array_len, + .. + } = array_desc; + match array_type { + RefDataType::U8Array => free_dynamic_u8_array(ptr, array_len), + RefDataType::I32Array => free_dynamic_i32_array(ptr, array_len), + RefDataType::DoubleArray => free_dynamic_double_array(ptr, array_len), + RefDataType::FloatArray => free_dynamic_float_array(ptr, array_len), + RefDataType::StringArray => free_dynamic_string_array(ptr, array_len), + } + } else if let FFITag::Function = ffi_tag { + let func_desc = get_func_desc(&obj); + if func_desc.need_free { + let _ = free(*(ptr as *mut *mut Closure) as *mut c_void); + } + } else { + // struct + free_struct_memory(*(ptr as *mut *mut c_void), obj, PointerType::CPointer); + free(*(ptr as *mut *mut c_void)) + } + } + _ => panic!("free c pointer memory error"), + } +} + +unsafe fn free_dynamic_string_array(ptr: *mut c_void, array_len: usize) { + let v = Vec::from_raw_parts(*(ptr as *mut *mut *mut c_char), array_len, array_len); + v.into_iter().for_each(|str_ptr| { + let _ = CString::from_raw(str_ptr); + }); +} +unsafe fn free_dynamic_i32_array(ptr: *mut c_void, array_len: usize) { + let _ = Vec::from_raw_parts(*(ptr as *mut *mut c_int), array_len, array_len); +} +unsafe fn free_dynamic_double_array(ptr: *mut c_void, array_len: usize) { + let _ = Vec::from_raw_parts(*(ptr as *mut *mut c_double), array_len, array_len); +} +unsafe fn free_dynamic_float_array(ptr: *mut c_void, array_len: usize) { + let _ = Vec::from_raw_parts(*(ptr as *mut *mut c_float), array_len, array_len); +} +unsafe fn free_dynamic_u8_array(ptr: *mut c_void, array_len: usize) { + let _ = Vec::from_raw_parts(*(ptr as *mut *mut c_char), array_len, array_len); +} diff --git a/src/define.rs b/src/define.rs index e3746ec..3fe7a0a 100644 --- a/src/define.rs +++ b/src/define.rs @@ -4,6 +4,7 @@ use libffi_sys::{ffi_cif, ffi_type}; use napi::bindgen_prelude::{Error, Result, Status as NapiStatus}; use napi::{bindgen_prelude::*, JsBufferValue}; use napi::{Env, JsExternal, JsObject, JsUnknown}; +use std::collections::HashMap; use std::hash::Hash; pub enum FFIError { @@ -73,13 +74,17 @@ where unsafe { JsObject::to_napi_value(raw_env, obj) } } } - -pub struct FFIARRARYDESC<'a> { +#[derive(Debug)] +pub struct FFIARRARYDESC { pub dynamic_array: bool, pub array_type: RefDataType, pub array_len: usize, - pub array_value: Option<&'a RsArgsValue>, } + +pub struct FFIFUNCDESC { + pub need_free: bool, +} + #[napi] #[derive(Debug)] pub enum DataType { @@ -167,7 +172,7 @@ pub fn rs_value_to_ffi_type(value: &RsArgsValue) -> Type { pub fn number_to_basic_data_type(value: i32) -> BasicDataType { if is_array_type(&value) { - panic!("If you want to use array type as return value type, you must use arrayConstrutor describe array type in retType field") + panic!("In the latest ffi-rs version, please use ffi-rs.arrayConstrutor to describe array type") } match value { 0 => BasicDataType::String, @@ -216,10 +221,32 @@ pub enum RsArgsValue { Object(IndexMap), Boolean(bool), Void(()), - Function(JsObject, JsFunction), + Function(IndexMap, JsFunction), External(JsExternal), } - +impl Clone for RsArgsValue { + fn clone(&self) -> Self { + match self { + RsArgsValue::String(s) => RsArgsValue::String(s.clone()), + RsArgsValue::U8(u) => RsArgsValue::U8(*u), + RsArgsValue::I32(i) => RsArgsValue::I32(*i), + RsArgsValue::I64(i) => RsArgsValue::I64(*i), + RsArgsValue::U64(u) => RsArgsValue::U64(*u), + RsArgsValue::Float(f) => RsArgsValue::Float(*f), + RsArgsValue::Double(d) => RsArgsValue::Double(*d), + RsArgsValue::I32Array(vec) => RsArgsValue::I32Array(vec.clone()), + RsArgsValue::StringArray(vec) => RsArgsValue::StringArray(vec.clone()), + RsArgsValue::DoubleArray(vec) => RsArgsValue::DoubleArray(vec.clone()), + RsArgsValue::FloatArray(vec) => RsArgsValue::FloatArray(vec.clone()), + RsArgsValue::Object(map) => RsArgsValue::Object(map.clone()), + RsArgsValue::Boolean(b) => RsArgsValue::Boolean(*b), + RsArgsValue::Void(()) => RsArgsValue::Void(()), + RsArgsValue::U8Array(_, _) => panic!("U8Array is buffer cannot be cloned"), + RsArgsValue::Function(_, _) => panic!("Function cannot be cloned"), + RsArgsValue::External(_) => panic!("External cannot be cloned"), + } + } +} unsafe impl Send for RsArgsValue {} unsafe impl Sync for RsArgsValue {} @@ -261,6 +288,7 @@ pub struct FFIParams { pub params_value: Vec, pub errno: Option, pub run_in_new_thread: Option, + pub free_result_memory: bool, } pub struct FFICALLPARAMS { @@ -269,6 +297,10 @@ pub struct FFICALLPARAMS { pub arg_values_c_void: Vec<*mut c_void>, pub ret_type_rs: RsArgsValue, pub errno: Option, + pub free_result_memory: bool, + pub params_type_rs: Vec, + pub r_type_p: *mut *mut ffi_type, + pub arg_types_p: *mut Vec<*mut ffi_type>, } pub struct BarePointerWrap(pub *mut c_void); unsafe impl Send for FFICALL {} @@ -289,6 +321,19 @@ pub struct CreatePointerParams { pub params_type: Vec, pub params_value: Vec, } +#[derive(Debug)] +#[napi] +pub enum PointerType { + RsPointer, + CPointer, +} + +#[napi(object)] +pub struct FreePointerParams { + pub params_type: Vec, + pub params_value: Vec, + pub pointer_type: PointerType, +} #[napi(object)] pub struct StorePointerParams { @@ -310,6 +355,9 @@ pub const ARRAY_VALUE_TAG: &str = "value"; pub const FFI_TAG_FIELD: &str = "ffiTypeTag"; pub const ARRAY_FFI_TAG: &str = "array"; pub const FUNCTION_FFI_TAG: &str = "function"; +pub const FUNCTION_FREE_TAG: &str = "needFree"; +pub static mut CLOSURE_MAP: Option>> = None; + #[derive(Debug)] pub enum FFITag { Array, diff --git a/src/lib.rs b/src/lib.rs index 3610380..c556f88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,7 @@ use libffi_sys::{ }; use napi::{Env, JsExternal, JsUnknown, Result}; +use datatype::pointer::{free_c_pointer_memory, free_rs_pointer_memory}; use std::collections::HashMap; use std::ffi::c_void; use utils::dataprocess::{ @@ -37,7 +38,11 @@ unsafe fn create_pointer(env: Env, params: CreatePointerParams) -> Result = params_type + .into_iter() + .map(|param| type_define_to_rs_args(&env, param).unwrap()) + .collect(); + let (_, arg_values) = get_arg_types_values(&env, params_type_rs, params_value)?; let arg_values_c_void = get_value_pointer(&env, arg_values)?; arg_values_c_void @@ -75,7 +80,6 @@ unsafe fn unwrap_pointer(env: Env, params: Vec) -> Result) -> Result> { params @@ -87,11 +91,26 @@ unsafe fn wrap_pointer(env: Env, params: Vec) -> Result) { - params.into_iter().for_each(|js_external| { - let ptr = get_js_external_wrap_data(&env, js_external).unwrap(); - free(ptr) - }); +unsafe fn free_pointer(env: Env, params: FreePointerParams) { + let FreePointerParams { + params_type, + params_value, + pointer_type, + } = params; + let params_type_rs: Vec = params_type + .into_iter() + .map(|param| type_define_to_rs_args(&env, param).unwrap()) + .collect(); + params_value + .into_iter() + .zip(params_type_rs.into_iter()) + .for_each(|(js_external, ptr_desc)| { + let ptr = get_js_external_wrap_data(&env, js_external).unwrap(); + match pointer_type { + PointerType::CPointer => free_c_pointer_memory(ptr, ptr_desc, true), + PointerType::RsPointer => free_rs_pointer_memory(ptr, ptr_desc, true), + } + }); } #[napi] @@ -184,32 +203,37 @@ unsafe fn load(env: Env, params: FFIParams) -> napi::Result { params_value, errno, run_in_new_thread, + free_result_memory, } = params; let func = get_symbol(&library, &func_name)?; let params_type_len = params_type.len(); - let (mut arg_types, arg_values) = get_arg_types_values(&env, params_type, params_value)?; + let params_type_rs: Vec = params_type + .into_iter() + .map(|param| type_define_to_rs_args(&env, param).unwrap()) + .collect(); + let (mut arg_types, arg_values) = + get_arg_types_values(&env, params_type_rs.clone(), params_value)?; let mut arg_values_c_void = get_value_pointer(&env, arg_values)?; let ret_type_rs = type_define_to_rs_args(&env, ret_type)?; let r_type: *mut ffi_type = match ret_type_rs { RsArgsValue::I32(number) => { let ret_data_type = number_to_basic_data_type(number); match ret_data_type { - BasicDataType::U8 => Box::into_raw(Box::new(ffi_type_uint8)) as *mut ffi_type, - BasicDataType::I32 => Box::into_raw(Box::new(ffi_type_sint32)) as *mut ffi_type, - BasicDataType::I64 => Box::into_raw(Box::new(ffi_type_sint64)) as *mut ffi_type, - BasicDataType::U64 => Box::into_raw(Box::new(ffi_type_uint64)) as *mut ffi_type, - BasicDataType::String => Box::into_raw(Box::new(ffi_type_pointer)) as *mut ffi_type, - BasicDataType::Void => Box::into_raw(Box::new(ffi_type_void)) as *mut ffi_type, - BasicDataType::Float => Box::into_raw(Box::new(ffi_type_float)) as *mut ffi_type, - BasicDataType::Double => Box::into_raw(Box::new(ffi_type_double)) as *mut ffi_type, - BasicDataType::Boolean => Box::into_raw(Box::new(ffi_type_uint8)) as *mut ffi_type, - BasicDataType::External => Box::into_raw(Box::new(ffi_type_pointer)) as *mut ffi_type, + BasicDataType::U8 => &mut ffi_type_uint8 as *mut ffi_type, + BasicDataType::I32 => &mut ffi_type_sint32 as *mut ffi_type, + BasicDataType::I64 => &mut ffi_type_sint64 as *mut ffi_type, + BasicDataType::U64 => &mut ffi_type_uint64 as *mut ffi_type, + BasicDataType::String => &mut ffi_type_pointer as *mut ffi_type, + BasicDataType::Void => &mut ffi_type_void as *mut ffi_type, + BasicDataType::Float => &mut ffi_type_float as *mut ffi_type, + BasicDataType::Double => &mut ffi_type_double as *mut ffi_type, + BasicDataType::Boolean => &mut ffi_type_uint8 as *mut ffi_type, + BasicDataType::External => &mut ffi_type_pointer as *mut ffi_type, } } - RsArgsValue::Object(_) => Box::into_raw(Box::new(ffi_type_pointer)) as *mut ffi_type, - _ => Box::into_raw(Box::new(ffi_type_void)) as *mut ffi_type, + RsArgsValue::Object(_) => &mut ffi_type_pointer as *mut ffi_type, + _ => &mut ffi_type_void as *mut ffi_type, }; - let mut cif = ffi_cif { abi: ffi_abi_FFI_DEFAULT_ABI, nargs: params_type_len as u32, @@ -230,8 +254,8 @@ unsafe fn load(env: Env, params: FFIParams) -> napi::Result { arg_types.as_mut_ptr(), ); if run_in_new_thread.is_some() && run_in_new_thread.unwrap() { - Box::into_raw(Box::new(r_type)); - Box::into_raw(Box::new(arg_types)); + let r_type_p = Box::into_raw(Box::new(r_type)); + let arg_types_p = Box::into_raw(Box::new(arg_types)); use napi::Task; impl Task for FFICALL { type Output = BarePointerWrap; @@ -257,10 +281,31 @@ unsafe fn load(env: Env, params: FFIParams) -> napi::Result { fn resolve(&mut self, env: Env, output: Self::Output) -> Result { let FFICALLPARAMS { - ret_type_rs, errno, .. + ret_type_rs, + errno, + arg_values_c_void, + free_result_memory, + params_type_rs, + cif, + r_type_p, + arg_types_p, + .. } = &mut self.data; unsafe { let call_result = get_js_unknown_from_pointer(&env, &ret_type_rs, output.0); + if *free_result_memory { + free_c_pointer_memory(output.0, ret_type_rs.clone(), false); + } + arg_values_c_void + .into_iter() + .zip(params_type_rs.into_iter()) + .for_each(|(ptr, ptr_desc)| { + free_rs_pointer_memory(*ptr, ptr_desc.clone(), false); + }); + let _ = Box::from_raw(*cif); + let _ = Box::from_raw(*r_type_p); + let _ = Box::from_raw(*arg_types_p); + free(output.0); if errno.is_some() && errno.unwrap() { add_errno(&env, call_result?) } else { @@ -275,13 +320,26 @@ unsafe fn load(env: Env, params: FFIParams) -> napi::Result { ret_type_rs, fn_pointer: func, errno, + free_result_memory, + params_type_rs, + r_type_p, + arg_types_p, }); let async_work_promise = env.spawn(task)?; Ok(async_work_promise.promise_object().into_unknown()) } else { - let result = malloc(std::mem::size_of::<*mut c_void>()); + let result = &mut () as *mut _ as *mut c_void; ffi_call(&mut cif, Some(func), result, arg_values_c_void.as_mut_ptr()); let call_result = get_js_unknown_from_pointer(&env, &ret_type_rs, result); + if free_result_memory { + free_c_pointer_memory(result, ret_type_rs, false); + } + arg_values_c_void + .into_iter() + .zip(params_type_rs.into_iter()) + .for_each(|(ptr, ptr_desc)| { + free_rs_pointer_memory(ptr, ptr_desc, false); + }); if errno.is_some() && errno.unwrap() { add_errno(&env, call_result?) } else { diff --git a/src/utils/dataprocess.rs b/src/utils/dataprocess.rs index 3e047d7..941669d 100644 --- a/src/utils/dataprocess.rs +++ b/src/utils/dataprocess.rs @@ -18,6 +18,7 @@ use napi::{ bindgen_prelude::*, Env, JsBoolean, JsBuffer, JsExternal, JsNumber, JsObject, JsString, JsUnknown, NapiRaw, }; +use std::collections::HashMap; use std::ffi::CStr; use std::fmt::format; @@ -68,17 +69,28 @@ pub fn get_array_desc(obj: &IndexMap) -> FFIARRARYDESC { array_len, dynamic_array: array_dynamic, array_type, - array_value: obj.get(ARRAY_VALUE_TAG), } } +pub fn get_array_value(obj: &mut IndexMap) -> Option { + obj.remove(ARRAY_VALUE_TAG) +} + +pub fn get_func_desc(obj: &IndexMap) -> FFIFUNCDESC { + let mut need_free = false; + if let RsArgsValue::Boolean(val) = obj.get(FUNCTION_FREE_TAG).unwrap() { + need_free = *val + } + FFIFUNCDESC { need_free } +} + pub fn js_number_to_i32(js_number: JsNumber) -> i32 { js_number.try_into().unwrap() } pub unsafe fn get_arg_types_values( env: &Env, - params_type: Vec, + params_type: Vec, params_value: Vec, ) -> Result<(Vec<*mut ffi_type>, Vec)> { if params_type.len() != params_value.len() { @@ -93,138 +105,140 @@ pub unsafe fn get_arg_types_values( .into_iter() .zip(params_value.into_iter()) .map(|(param, value)| { - let value_type = param.get_type()?; - let res = match value_type { - ValueType::Number => { - let param_data_type = - number_to_data_type(create_js_value_unchecked::(env, param).try_into()?); + let res = match param { + RsArgsValue::I32(number) => { + let param_data_type = number_to_basic_data_type(number); match param_data_type { - DataType::I32 => { - let arg_type = Box::into_raw(Box::new(ffi_type_sint32)) as *mut ffi_type; + BasicDataType::I32 => { + let arg_type = &mut ffi_type_sint32 as *mut ffi_type; let arg_val: i32 = create_js_value_unchecked::(env, value).try_into()?; (arg_type, RsArgsValue::I32(arg_val)) } - DataType::U8 => { - let arg_type = Box::into_raw(Box::new(ffi_type_sint32)) as *mut ffi_type; + BasicDataType::U8 => { + let arg_type = &mut ffi_type_sint32 as *mut ffi_type; let arg_val: u32 = create_js_value_unchecked::(env, value).try_into()?; (arg_type, RsArgsValue::U8(arg_val as u8)) } - DataType::I64 => { - let arg_type = Box::into_raw(Box::new(ffi_type_sint64)) as *mut ffi_type; + BasicDataType::I64 => { + let arg_type = &mut ffi_type_sint64 as *mut ffi_type; let arg_val: i64 = create_js_value_unchecked::(env, value).try_into()?; (arg_type, RsArgsValue::I64(arg_val)) } - DataType::U64 => { - let arg_type = Box::into_raw(Box::new(ffi_type_uint64)) as *mut ffi_type; + BasicDataType::U64 => { + let arg_type = &mut ffi_type_uint64 as *mut ffi_type; let arg_val: i64 = create_js_value_unchecked::(env, value).try_into()?; (arg_type, RsArgsValue::U64(arg_val as u64)) } - DataType::Float => { - let arg_type = Box::into_raw(Box::new(ffi_type_float)) as *mut ffi_type; + BasicDataType::Float => { + let arg_type = &mut ffi_type_float as *mut ffi_type; let arg_val: f64 = create_js_value_unchecked::(env, value).try_into()?; (arg_type, RsArgsValue::Float(arg_val as f32)) } - DataType::Double => { - let arg_type = Box::into_raw(Box::new(ffi_type_double)) as *mut ffi_type; + BasicDataType::Double => { + let arg_type = &mut ffi_type_double as *mut ffi_type; let arg_val: f64 = create_js_value_unchecked::(env, value).try_into()?; (arg_type, RsArgsValue::Double(arg_val)) } - DataType::String => { - let arg_type = Box::into_raw(Box::new(ffi_type_pointer)) as *mut ffi_type; + BasicDataType::String => { + let arg_type = &mut ffi_type_pointer as *mut ffi_type; let arg_val: String = js_string_to_string(create_js_value_unchecked::(env, value))?; (arg_type, RsArgsValue::String(arg_val)) } - DataType::U8Array => { - let arg_type = Box::into_raw(Box::new(ffi_type_pointer)) as *mut ffi_type; - let js_buffer: JsBuffer = value.try_into()?; - ( - arg_type, - RsArgsValue::U8Array(Some(js_buffer.into_value()?), None), - ) - } - DataType::I32Array => { - let arg_type = &mut ffi_type_pointer as *mut ffi_type; - let js_object = create_js_value_unchecked::(env, value); - let arg_val = vec![0; js_object.get_array_length()? as usize] - .iter() - .enumerate() - .map(|(index, _)| { - let js_element: JsNumber = js_object.get_element(index as u32).unwrap(); - js_element.get_int32().unwrap() - }) - .collect::>(); - (arg_type, RsArgsValue::I32Array(arg_val)) - } - DataType::DoubleArray => { - let arg_type = &mut ffi_type_pointer as *mut ffi_type; - let js_object = create_js_value_unchecked::(env, value); - let arg_val = vec![0; js_object.get_array_length()? as usize] - .iter() - .enumerate() - .map(|(index, _)| { - let js_element: JsNumber = js_object.get_element(index as u32).unwrap(); - js_element.get_double().unwrap() - }) - .collect::>(); - - (arg_type, RsArgsValue::DoubleArray(arg_val)) - } - DataType::FloatArray => { - let arg_type = &mut ffi_type_pointer as *mut ffi_type; - let js_object = create_js_value_unchecked::(env, value); - let arg_val = vec![0; js_object.get_array_length()? as usize] - .iter() - .enumerate() - .map(|(index, _)| { - let js_element: JsNumber = js_object.get_element(index as u32).unwrap(); - js_element.get_double().unwrap() as f32 - }) - .collect::>(); - (arg_type, RsArgsValue::FloatArray(arg_val)) - } - DataType::StringArray => { - let arg_type = &mut ffi_type_pointer as *mut ffi_type; - let js_object = create_js_value_unchecked::(env, value); - let arg_val = js_object.to_rs_array()?; - (arg_type, RsArgsValue::StringArray(arg_val)) - } - DataType::Boolean => { + BasicDataType::Boolean => { let arg_type = &mut ffi_type_uint8 as *mut ffi_type; let arg_val: bool = create_js_value_unchecked::(env, value).get_value()?; (arg_type, RsArgsValue::Boolean(arg_val)) } - DataType::Void => { + BasicDataType::Void => { let arg_type = &mut ffi_type_void as *mut ffi_type; (arg_type, RsArgsValue::Void(())) } - DataType::External => { + BasicDataType::External => { let arg_type = &mut ffi_type_pointer as *mut ffi_type; let js_external: JsExternal = value.try_into()?; (arg_type, RsArgsValue::External(js_external)) } } } - ValueType::Object => { - let params_type_object: JsObject = create_js_value_unchecked::(env, param); - let params_type_object_rs = type_object_to_rs_struct(env, ¶ms_type_object)?; + RsArgsValue::Object(params_type_object_rs) => { let arg_type = &mut ffi_type_pointer as *mut ffi_type; - if let FFITag::Function = get_ffi_tag(¶ms_type_object_rs) { + if let FFITag::Array = get_ffi_tag(¶ms_type_object_rs) { + let array_desc = get_array_desc(¶ms_type_object_rs); + let FFIARRARYDESC { array_type, .. } = array_desc; + match array_type { + RefDataType::U8Array => { + let arg_type = &mut ffi_type_pointer as *mut ffi_type; + let js_buffer: JsBuffer = value.try_into()?; + ( + arg_type, + RsArgsValue::U8Array(Some(js_buffer.into_value()?), None), + ) + } + RefDataType::I32Array => { + let arg_type = &mut ffi_type_pointer as *mut ffi_type; + let js_object = create_js_value_unchecked::(env, value); + let arg_val = vec![0; js_object.get_array_length()? as usize] + .iter() + .enumerate() + .map(|(index, _)| { + let js_element: JsNumber = js_object.get_element(index as u32).unwrap(); + js_element.get_int32().unwrap() + }) + .collect::>(); + (arg_type, RsArgsValue::I32Array(arg_val)) + } + + RefDataType::FloatArray => { + let arg_type = &mut ffi_type_pointer as *mut ffi_type; + let js_object = create_js_value_unchecked::(env, value); + let arg_val = vec![0; js_object.get_array_length()? as usize] + .iter() + .enumerate() + .map(|(index, _)| { + let js_element: JsNumber = js_object.get_element(index as u32).unwrap(); + js_element.get_double().unwrap() as f32 + }) + .collect::>(); + (arg_type, RsArgsValue::FloatArray(arg_val)) + } + RefDataType::DoubleArray => { + let arg_type = &mut ffi_type_pointer as *mut ffi_type; + let js_object = create_js_value_unchecked::(env, value); + let arg_val = vec![0; js_object.get_array_length()? as usize] + .iter() + .enumerate() + .map(|(index, _)| { + let js_element: JsNumber = js_object.get_element(index as u32).unwrap(); + js_element.get_double().unwrap() + }) + .collect::>(); + + (arg_type, RsArgsValue::DoubleArray(arg_val)) + } + RefDataType::StringArray => { + let arg_type = &mut ffi_type_pointer as *mut ffi_type; + let js_object = create_js_value_unchecked::(env, value); + let arg_val = js_object.to_rs_array()?; + (arg_type, RsArgsValue::StringArray(arg_val)) + } + } + } else if let FFITag::Function = get_ffi_tag(¶ms_type_object_rs) { let params_val_function: JsFunction = value.try_into()?; let arg_type = &mut ffi_type_pointer as *mut ffi_type; ( arg_type, - RsArgsValue::Function(params_type_object, params_val_function), + RsArgsValue::Function(params_type_object_rs, params_val_function), ) } else { let params_value_object = create_js_value_unchecked::(env, value); let index_map = - get_params_value_rs_struct(&env, ¶ms_type_object, ¶ms_value_object); + get_params_value_rs_struct(&env, params_type_object_rs, ¶ms_value_object); (arg_type, RsArgsValue::Object(index_map.unwrap())) } } - _ => panic!("unsupported params type {:?}", value_type), + _ => panic!("unsupported params type {:?}", param), }; Ok(res) }) @@ -338,12 +352,9 @@ pub unsafe fn get_value_pointer( RsArgsValue::Function(func_desc, js_function) => { use libffi::low; use libffi::middle::*; - let func_args_type: JsObject = func_desc - .get_property(env.create_string("paramsType").unwrap()) - .unwrap(); - let func_args_type_rs = type_object_to_rs_vector(env, &func_args_type)?; - let tsfn: ThreadsafeFunction, ErrorStrategy::Fatal> = (&js_function) - .create_threadsafe_function(0, |ctx| { + let func_args_type = func_desc.get("paramsType").unwrap().clone(); + let mut tsfn: ThreadsafeFunction, ErrorStrategy::Fatal> = + (&js_function).create_threadsafe_function(0, |ctx| { let value: Vec = ctx.value; let js_call_params: Vec = value .into_iter() @@ -352,7 +363,7 @@ pub unsafe fn get_value_pointer( Ok(js_call_params) })?; - + let tsfn_ptr = Box::into_raw(Box::new(tsfn)); unsafe extern "C" fn lambda_callback)>( _cif: &low::ffi_cif, result: &mut *mut c_void, @@ -364,32 +375,46 @@ pub unsafe fn get_value_pointer( .collect(); userdata(params); } - let cif = Cif::new( - func_args_type_rs - .iter() - .map(|arg_type| rs_value_to_ffi_type(arg_type)), - Type::void(), - ); - let lambda = move |args: Vec<*mut c_void>| { - let value: Vec = args - .into_iter() - .enumerate() - .map(|(index, c_param)| { - let arg_type = &(func_args_type_rs)[index]; - let param = get_rs_value_from_pointer(env, arg_type, c_param, true); - param - }) - .collect(); - tsfn.call(value, ThreadsafeFunctionCallMode::Blocking); + let (cif, lambda) = if let RsArgsValue::Object(func_args_type_rs) = func_args_type { + let cif = Cif::new( + func_args_type_rs + .values() + .into_iter() + .map(|val| rs_value_to_ffi_type(val)), + Type::void(), + ); + let lambda = move |args: Vec<*mut c_void>| { + let value: Vec = args + .into_iter() + .enumerate() + .map(|(index, c_param)| { + let arg_type = func_args_type_rs.get(&index.to_string()).unwrap(); + let param = get_rs_value_from_pointer(env, arg_type, c_param, true); + free_c_pointer_memory(c_param, arg_type.clone(), false); + param + }) + .collect(); + (*tsfn_ptr).call(value, ThreadsafeFunctionCallMode::Blocking); + }; + (cif, lambda) + } else { + return Err(FFIError::Panic(format!("generate cif error")).into()); }; - - let closure = Box::into_raw(Box::new(Closure::new( - cif, - lambda_callback, - &*Box::into_raw(Box::new(lambda)), - ))); - - Ok(std::mem::transmute((*closure).code_ptr())) + let lambda_ptr = Box::into_raw(Box::new(lambda)); + let closure = Box::into_raw(Box::new(Closure::new(cif, lambda_callback, &*lambda_ptr))); + if CLOSURE_MAP.is_none() { + CLOSURE_MAP = Some(HashMap::new()) + } + let code_ptr = std::mem::transmute((*closure).code_ptr()); + CLOSURE_MAP.as_mut().unwrap().insert( + code_ptr, + vec![ + tsfn_ptr as *mut c_void, + closure as *mut c_void, + lambda_ptr as *mut c_void, + ], + ); + Ok(code_ptr) // has been deprecated // Ok( @@ -413,119 +438,69 @@ pub unsafe fn get_value_pointer( pub unsafe fn get_params_value_rs_struct( env: &Env, - params_type_object: &JsObject, + params_type_object: IndexMap, params_value_object: &JsObject, ) -> Result> { let mut index_map = IndexMap::new(); - let parse_result: Result<()> = JsObject::keys(¶ms_value_object)? - .into_iter() - .try_for_each(|field| { - let field_type: JsUnknown = params_type_object.get_named_property(&field)?; - match field_type.get_type()? { - ValueType::Number => { - let data_type: DataType = - number_to_data_type(create_js_value_unchecked::(env, field_type).try_into()?); - let val = match data_type { - DataType::String => { - let val: JsString = params_value_object.get_named_property(&field)?; - let val: String = js_string_to_string(val)?; - RsArgsValue::String(val) - } - DataType::U8 => { - let val: JsNumber = params_value_object.get_named_property(&field)?; - let val: u32 = val.try_into()?; - RsArgsValue::U8(val as u8) - } - DataType::I32 => { - let val: JsNumber = params_value_object.get_named_property(&field)?; - let val: i32 = val.try_into()?; - RsArgsValue::I32(val) - } - DataType::I64 => { - let val: JsNumber = params_value_object.get_named_property(&field)?; - let val: i64 = val.try_into()?; - RsArgsValue::I64(val) - } - DataType::U64 => { - let val: JsNumber = params_value_object.get_named_property(&field)?; - let val: i64 = val.try_into()?; - RsArgsValue::U64(val as u64) - } - DataType::Boolean => { - let val: JsBoolean = params_value_object.get_named_property(&field)?; - let val: bool = val.get_value()?; - RsArgsValue::Boolean(val) - } - DataType::Float => { - let val: JsNumber = params_value_object.get_named_property(&field)?; - let val: f64 = val.try_into()?; - RsArgsValue::Float(val as f32) - } - DataType::Double => { - let val: JsNumber = params_value_object.get_named_property(&field)?; - let val: f64 = val.try_into()?; - RsArgsValue::Double(val) - } - DataType::StringArray => { - let js_array: JsObject = params_value_object.get_named_property(&field)?; - let arg_val = js_array.to_rs_array()?; - RsArgsValue::StringArray(arg_val) - } - DataType::DoubleArray => { - let js_array: JsObject = params_value_object.get_named_property(&field)?; - let arg_val: Vec = js_array.to_rs_array()?; - RsArgsValue::DoubleArray(arg_val) - } - DataType::FloatArray => { - let js_array: JsObject = params_value_object.get_named_property(&field)?; - let arg_val: Vec = js_array - .to_rs_array()? - .into_iter() - .map(|item: f64| item as f32) - .collect(); - RsArgsValue::FloatArray(arg_val) - } - DataType::I32Array => { - let js_array: JsObject = params_value_object.get_named_property(&field)?; - let arg_val = js_array.to_rs_array()?; - RsArgsValue::I32Array(arg_val) - } - DataType::U8Array => { - let js_buffer: JsBuffer = params_value_object.get_named_property(&field)?; - RsArgsValue::U8Array(Some(js_buffer.into_value()?), None) - } - DataType::External => { - let val: JsExternal = params_value_object.get_named_property(&field)?; - RsArgsValue::External(val) - } - DataType::Void => RsArgsValue::Void(()), - }; - index_map.insert(field, val); - } - - ValueType::Object => { - let params_type = create_js_value_unchecked::(env, field_type); - let params_value: JsObject = params_value_object.get_named_property(&field)?; - let mut params_type_rs_value = type_object_to_rs_struct(env, ¶ms_type)?; - if let FFITag::Array = get_ffi_tag(¶ms_type_rs_value) { - let array_desc = get_array_desc(¶ms_type_rs_value); - let FFIARRARYDESC { array_type, .. } = array_desc; - let array_value = match array_type { - RefDataType::U8Array => { - let js_buffer: JsBuffer = params_value_object.get_named_property(&field)?; - RsArgsValue::U8Array(Some(js_buffer.into_value()?), None) + let parse_result: Result<()> = + params_type_object + .into_iter() + .try_for_each(|(field, field_type)| { + match field_type { + RsArgsValue::I32(data_type_number) => { + let data_type: DataType = number_to_data_type(data_type_number); + let val = match data_type { + DataType::String => { + let val: JsString = params_value_object.get_named_property(&field)?; + let val: String = js_string_to_string(val)?; + RsArgsValue::String(val) } - RefDataType::I32Array => { + DataType::U8 => { + let val: JsNumber = params_value_object.get_named_property(&field)?; + let val: u32 = val.try_into()?; + RsArgsValue::U8(val as u8) + } + DataType::I32 => { + let val: JsNumber = params_value_object.get_named_property(&field)?; + let val: i32 = val.try_into()?; + RsArgsValue::I32(val) + } + DataType::I64 => { + let val: JsNumber = params_value_object.get_named_property(&field)?; + let val: i64 = val.try_into()?; + RsArgsValue::I64(val) + } + DataType::U64 => { + let val: JsNumber = params_value_object.get_named_property(&field)?; + let val: i64 = val.try_into()?; + RsArgsValue::U64(val as u64) + } + DataType::Boolean => { + let val: JsBoolean = params_value_object.get_named_property(&field)?; + let val: bool = val.get_value()?; + RsArgsValue::Boolean(val) + } + DataType::Float => { + let val: JsNumber = params_value_object.get_named_property(&field)?; + let val: f64 = val.try_into()?; + RsArgsValue::Float(val as f32) + } + DataType::Double => { + let val: JsNumber = params_value_object.get_named_property(&field)?; + let val: f64 = val.try_into()?; + RsArgsValue::Double(val) + } + DataType::StringArray => { let js_array: JsObject = params_value_object.get_named_property(&field)?; let arg_val = js_array.to_rs_array()?; - RsArgsValue::I32Array(arg_val) + RsArgsValue::StringArray(arg_val) } - RefDataType::DoubleArray => { + DataType::DoubleArray => { let js_array: JsObject = params_value_object.get_named_property(&field)?; - let arg_val = js_array.to_rs_array()?; + let arg_val: Vec = js_array.to_rs_array()?; RsArgsValue::DoubleArray(arg_val) } - RefDataType::FloatArray => { + DataType::FloatArray => { let js_array: JsObject = params_value_object.get_named_property(&field)?; let arg_val: Vec = js_array .to_rs_array()? @@ -534,32 +509,78 @@ pub unsafe fn get_params_value_rs_struct( .collect(); RsArgsValue::FloatArray(arg_val) } - RefDataType::StringArray => { + DataType::I32Array => { let js_array: JsObject = params_value_object.get_named_property(&field)?; let arg_val = js_array.to_rs_array()?; - RsArgsValue::StringArray(arg_val) + RsArgsValue::I32Array(arg_val) + } + DataType::U8Array => { + let js_buffer: JsBuffer = params_value_object.get_named_property(&field)?; + RsArgsValue::U8Array(Some(js_buffer.into_value()?), None) + } + DataType::External => { + let val: JsExternal = params_value_object.get_named_property(&field)?; + RsArgsValue::External(val) } + DataType::Void => RsArgsValue::Void(()), }; - params_type_rs_value.insert(ARRAY_VALUE_TAG.to_string(), array_value); - index_map.insert(field, RsArgsValue::Object(params_type_rs_value)); - } else { - let map = get_params_value_rs_struct(env, ¶ms_type, ¶ms_value); - index_map.insert(field, RsArgsValue::Object(map?)); + index_map.insert(field, val); } - } - _ => { - return Err( - FFIError::UnsupportedValueType(format!( - "Get field {:?} received {:?} but params type only supported number or object ", - field, - field_type.get_type().unwrap() - )) - .into(), - ) - } - }; - Ok(()) - }); + + RsArgsValue::Object(mut params_type_rs_value) => { + let params_value: JsObject = params_value_object.get_named_property(&field)?; + if let FFITag::Array = get_ffi_tag(¶ms_type_rs_value) { + let array_desc = get_array_desc(¶ms_type_rs_value); + let FFIARRARYDESC { array_type, .. } = array_desc; + let array_value = match array_type { + RefDataType::U8Array => { + let js_buffer: JsBuffer = params_value_object.get_named_property(&field)?; + RsArgsValue::U8Array(Some(js_buffer.into_value()?), None) + } + RefDataType::I32Array => { + let js_array: JsObject = params_value_object.get_named_property(&field)?; + let arg_val = js_array.to_rs_array()?; + RsArgsValue::I32Array(arg_val) + } + RefDataType::DoubleArray => { + let js_array: JsObject = params_value_object.get_named_property(&field)?; + let arg_val = js_array.to_rs_array()?; + RsArgsValue::DoubleArray(arg_val) + } + RefDataType::FloatArray => { + let js_array: JsObject = params_value_object.get_named_property(&field)?; + let arg_val: Vec = js_array + .to_rs_array()? + .into_iter() + .map(|item: f64| item as f32) + .collect(); + RsArgsValue::FloatArray(arg_val) + } + RefDataType::StringArray => { + let js_array: JsObject = params_value_object.get_named_property(&field)?; + let arg_val = js_array.to_rs_array()?; + RsArgsValue::StringArray(arg_val) + } + }; + params_type_rs_value.insert(ARRAY_VALUE_TAG.to_string(), array_value); + index_map.insert(field, RsArgsValue::Object(params_type_rs_value)); + } else { + let map = get_params_value_rs_struct(env, params_type_rs_value, ¶ms_value); + index_map.insert(field, RsArgsValue::Object(map?)); + } + } + _ => { + return Err( + FFIError::UnsupportedValueType(format!( + "Get field {:?} received {:?} but params type only supported number or object ", + field, field_type + )) + .into(), + ) + } + }; + Ok(()) + }); match parse_result { Ok(_) => Ok(index_map), Err(e) => Err(e), @@ -571,12 +592,11 @@ pub unsafe fn type_object_to_rs_struct( params_type: &JsObject, ) -> Result> { let mut index_map = IndexMap::new(); - let parse_result: Result<()> = JsObject::keys(params_type) - .unwrap() + let parse_result: Result<()> = JsObject::keys(params_type)? .into_iter() .try_for_each(|field| { - let field_type: JsUnknown = params_type.get_named_property(&field).unwrap(); - match field_type.get_type().unwrap() { + let field_type: JsUnknown = params_type.get_named_property(&field)?; + match field_type.get_type()? { ValueType::Number => { let number: JsNumber = field_type.try_into()?; let val: i32 = number.try_into()?; @@ -657,8 +677,8 @@ pub unsafe fn type_object_to_rs_vector( // describe paramsType or retType, field can only be number or object pub unsafe fn type_define_to_rs_args(env: &Env, type_define: JsUnknown) -> Result { - let ret_value_type = type_define.get_type()?; - let ret_value = match ret_value_type { + let params_type_value_type = type_define.get_type()?; + let ret_value = match params_type_value_type { ValueType::Number => RsArgsValue::I32(js_number_to_i32(create_js_value_unchecked::( env, type_define, @@ -671,7 +691,7 @@ pub unsafe fn type_define_to_rs_args(env: &Env, type_define: JsUnknown) -> Resul return Err( FFIError::UnsupportedValueType(format!( "ret_value_type can only be number or object but receive {}", - ret_value_type + params_type_value_type )) .into(), ) diff --git a/test.ts b/test.ts index 8a76d72..f867eb0 100644 --- a/test.ts +++ b/test.ts @@ -10,12 +10,15 @@ import { restorePointer, unwrapPointer, wrapPointer, - define + freePointer, + define, + PointerType } from "./index" const platform = process.platform; const dynamicLib = platform === "win32" ? "./sum.dll" : "./libsum.so"; const logGreen = (text) => { + if (process.env.SILENT) return console.log('\x1b[32m%s\x1b[0m', text); } @@ -64,7 +67,7 @@ const testNumber = () => { }), ); } -const c = "foo"; +const c = "foo".repeat(100); const d = "bar" const testString = () => { equal( @@ -77,20 +80,6 @@ const testString = () => { paramsValue: [c, d], }), ); - let stringArr = [c, c.repeat(20)]; - deepStrictEqual( - stringArr, - load({ - library: "libsum", - funcName: "createArrayString", - retType: arrayConstructor({ - type: DataType.StringArray, - length: stringArr.length, - }), - paramsType: [DataType.StringArray, DataType.I32], - paramsValue: [stringArr, stringArr.length], - }), - ); } const testVoid = () => { equal( @@ -118,6 +107,24 @@ const testBool = () => { ); } const testArray = () => { + let stringArr = [c, c.repeat(200)]; + deepStrictEqual( + stringArr, + load({ + library: "libsum", + funcName: "createArrayString", + retType: arrayConstructor({ + type: DataType.StringArray, + length: stringArr.length, + }), + paramsType: [arrayConstructor({ + type: DataType.StringArray, + length: stringArr.length, + }), DataType.I32], + paramsValue: [stringArr, stringArr.length], + }), + ); + logGreen('test createArrayString succeed') let bigArr = new Array(100).fill(100); deepStrictEqual( bigArr, @@ -128,7 +135,10 @@ const testArray = () => { type: DataType.I32Array, length: bigArr.length, }), - paramsType: [DataType.I32Array, DataType.I32], + paramsType: [arrayConstructor({ + type: DataType.I32Array, + length: bigArr.length, + }), DataType.I32], paramsValue: [bigArr, bigArr.length], }), ); @@ -143,7 +153,10 @@ const testArray = () => { type: DataType.DoubleArray, length: bigDoubleArr.length, }), - paramsType: [DataType.DoubleArray, DataType.I32], + paramsType: [arrayConstructor({ + type: DataType.DoubleArray, + length: bigDoubleArr.length, + }), DataType.I32], paramsValue: [bigDoubleArr, bigDoubleArr.length], }), ); @@ -157,23 +170,27 @@ const testPointer = () => { retType: [DataType.I32], paramsValue: i32Ptr }) + freePointer({ + paramsType: [DataType.I32], + paramsValue: i32Ptr, + pointerType: PointerType.RsPointer + }) deepStrictEqual(i32Data[0], 100) logGreen('test create and restore i32 pointer success') + const stringPointer = createPointer({ + paramsType: [DataType.String], + paramsValue: ["foo"] + }) const stringData = restorePointer({ retType: [DataType.String], - paramsValue: createPointer({ - paramsType: [DataType.String], - paramsValue: ["foo"] - }) + paramsValue: stringPointer }) - logGreen('test string pointer success') - const ptr = load({ - library: "libsum", - funcName: "concatenateStrings", - retType: DataType.External, - paramsType: [DataType.String, DataType.String], - paramsValue: [c, d], + freePointer({ + paramsType: [DataType.String], + paramsValue: stringPointer, + pointerType: PointerType.RsPointer }) + logGreen('test create string pointer success') equal(load({ library: "libsum", funcName: "getStringFromPtr", @@ -184,6 +201,13 @@ const testPointer = () => { paramsValue: ["foo"] })), }), "foo") + const ptr = load({ + library: "libsum", + funcName: "concatenateStrings", + retType: DataType.External, + paramsType: [DataType.String, DataType.String], + paramsValue: [c, d], + }) const string = load({ library: "libsum", funcName: "getStringFromPtr", @@ -193,7 +217,7 @@ const testPointer = () => { }) equal(string, c + d) deepStrictEqual(stringData[0], "foo") - logGreen('string pointer success') + logGreen('test string pointer success') const restoreData = restorePointer({ retType: [arrayConstructor({ type: DataType.DoubleArray, @@ -312,65 +336,29 @@ const personType = { }), }; const testObject = () => { - const personObjType = { - age: DataType.I32, - doubleArray: DataType.DoubleArray, - parent: { - parent: {}, - age: DataType.I32, - doubleProps: DataType.Double, - name: DataType.String, - stringArray: DataType.StringArray, - doubleArray: DataType.DoubleArray, - i32Array: DataType.I32Array, - staticBytes: arrayConstructor({ - type: DataType.U8Array, - length: parent.staticBytes.length, - dynamicArray: false - }), - boolTrue: DataType.Boolean, - boolFalse: DataType.Boolean, - longVal: DataType.U64, - byte: DataType.U8, - byteArray: DataType.U8Array, - }, - doubleProps: DataType.Double, - name: DataType.String, - stringArray: DataType.StringArray, - i32Array: DataType.I32Array, - staticBytes: arrayConstructor({ - type: DataType.U8Array, - length: person.staticBytes.length, - dynamicArray: false - }), - boolTrue: DataType.Boolean, - boolFalse: DataType.Boolean, - longVal: DataType.U64, - byte: DataType.U8, - byteArray: DataType.U8Array, - } - const createdPerson = load({ - library: "libsum", - funcName: "createPerson", - retType: personType, - paramsType: [], - paramsValue: [], - }); - deepStrictEqual(createdPerson, person); - logGreen('test createdPerson succeed') const personObj = load({ library: "libsum", funcName: "getStruct", retType: personType, paramsType: [ - personObjType + personType ], paramsValue: [person], + freeResultMemory: false }); deepStrictEqual(person, personObj); logGreen('test getStruct succeed') + const createdPerson = load({ + library: "libsum", + funcName: "createPerson", + retType: personType, + paramsType: [], + paramsValue: [], + }); + deepStrictEqual(createdPerson, person); + logGreen('test createdPerson succeed') let personPointer = createPointer({ - paramsType: [personObjType], + paramsType: [personType], paramsValue: [person] }) const personObjByPointer = load({ @@ -380,12 +368,13 @@ const testObject = () => { paramsType: [ DataType.External ], + freeResultMemory: false, paramsValue: unwrapPointer(personPointer), }); deepStrictEqual(person, personObjByPointer); logGreen('test getStructByPointer succeed') personPointer = createPointer({ - paramsType: [personObjType], + paramsType: [personType], paramsValue: [person] }) const restorePersonObjByPointer = restorePointer({ @@ -409,7 +398,6 @@ const testRunInNewThread = () => { }) } const testFunction = () => { - let count = 0; const func = (a, b, c, d, e, f, g) => { equal(a, 100); equal(b, false); @@ -418,14 +406,28 @@ const testFunction = () => { deepStrictEqual(e, ["Hello", "world"]); deepStrictEqual(f, [101, 202, 303]); deepStrictEqual(g, person); - console.log("callback called"); - count++; - if (count === 4) { - logGreen("test succeed"); - if (!process.env.MEMORY) { - close("libsum"); - process.exit(0); - } + logGreen("test function succeed"); + // free function memory when it not in use + setTimeout(() => { + freePointer({ + paramsType: [funcConstructor({ + paramsType: [ + DataType.I32, + DataType.Boolean, + DataType.String, + DataType.Double, + arrayConstructor({ type: DataType.StringArray, length: 2 }), + arrayConstructor({ type: DataType.I32Array, length: 3 }), + personType, + ], + retType: DataType.Void, + })], + paramsValue: funcExternal, + pointerType: PointerType.RsPointer + }) + }, 1000) + if (!process.env.MEMORY) { + close("libsum"); } }; const funcExternal = createPointer({ @@ -443,24 +445,6 @@ const testFunction = () => { })], paramsValue: [func] }) - load({ - library: "libsum", - funcName: "callFunction", - retType: DataType.Void, - paramsType: [funcConstructor({ - paramsType: [ - DataType.I32, - DataType.Boolean, - DataType.String, - DataType.Double, - arrayConstructor({ type: DataType.StringArray, length: 2 }), - arrayConstructor({ type: DataType.I32Array, length: 3 }), - personType, - ], - retType: DataType.Void, - })], - paramsValue: [func] - }); load({ library: "libsum", funcName: "callFunction", @@ -492,6 +476,11 @@ const testCpp = () => { ], paramsValue: [classPointer], }) + freePointer({ + paramsType: [DataType.External], + paramsValue: [classPointer], + pointerType: PointerType.CPointer + }) } const testMainProgram = () => { if (platform !== 'win32') { @@ -522,25 +511,24 @@ const unitTest = () => { logGreen('test number succeed') testString() logGreen('test string succeed') + testDefine() + logGreen('test define succeed') testArray() logGreen('test array succeed') - testPointer() - logGreen('test createPointer succeed') testVoid() logGreen('test void succeed') testBool() logGreen('test bool succeed') - testObject() - logGreen('test object succeed') - testCpp() - logGreen('test cpp succeed') testMainProgram() logGreen('test main program succeed') testFunction() - logGreen('test function succeed') + testCpp() + logGreen('test cpp succeed') + testObject() + logGreen('test object succeed') + testPointer() + logGreen('test createPointer succeed') testRunInNewThread() - testDefine() - logGreen('test define succeed') }; unitTest();