Skip to content

Commit

Permalink
fix: add freePointer, free memory after ffi-call prevent memory leak (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangyuang committed May 20, 2024
1 parent 8b6453a commit f639628
Show file tree
Hide file tree
Showing 12 changed files with 1,526 additions and 614 deletions.
132 changes: 74 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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.
Expand All @@ -473,7 +487,6 @@ const ptr = load({

// wrapPtr type is *mut *mut c_char
const wrapPtr = wrapPointer([ptr])[0]
```

#### unwrapPointer

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -673,6 +684,11 @@ load({
],
paramsValue: [classPointer],
})
freePointer({
paramsType: [DataType.External],
paramsValue: [classPointer],
pointerType: PointerType.CPointer
})
```
## errno
Expand Down
13 changes: 6 additions & 7 deletions cpp/sum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -170,7 +170,6 @@ extern "C" void callFunction(FunctionPointer func) {

Person *p = createPerson();
func(a, b, c, d, stringArray, i32Array, p);
}
}

// 定义 C++ 类
Expand Down
5 changes: 5 additions & 0 deletions memory.js
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
9 changes: 7 additions & 2 deletions scripts/build.js
Original file line number Diff line number Diff line change
@@ -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'))

0 comments on commit f639628

Please sign in to comment.