ffi-rs
A module written in Rust and N-APi provides interface (FFI) features for Node.js
Description
ffi-rs is a module written in Rust and N-API that provides FFI (Foreign Function Interface) features for Node.js. It allows developers to call functions written in other languages such as C++, C, and Rust directly from JavaScript without writing any C++ code.
This module aims to provide similar functionality to the node-ffi module, but with a completely rewritten underlying codebase. The node-ffi module has been unmaintained for several years and is no longer usable, which is why ffi-rs was developed.
benchmark
$ node bench/bench.js
Running "ffi" suite...
Progress: 100%
ffi-napi:
2 028 ops/s, ±4.87% | slowest, 99.24% slower
koffi:
78 846 ops/s, ±13.74% | 70.63% slower
ffi-rs:
268 468 ops/s, ±0.27% | fastest
Finished 3 cases!
Fastest: ffi-rs
Slowest: ffi-napi
install
$ npm i ffi-rs
Support type
Currently, ffi-rs only supports there types of parameters and return values. However, support for more types will be added in the future based on actual usage scenarios.
- string
- i32(number)
- void(undefined)
- double
- boolean
- i32Array
- stringArray
- doubleArray
- object
Support Platform
- darwin-x64
- darwin-arm64
- linux-x64-gnu
- win32-x64-msvc
- linux-arm64-gnu
- linux-arm64-musl
Usage
Here is an example of how to use ffi-rs:
For below c++ code, we compile this file into a dynamic library
#include <cstdio>
#include <cstring>
#include <iostream>
#include <string>
extern "C" int sum(int a, int b) { return a + b; }
extern "C" double doubleSum(double a, double b) { return a + b; }
extern "C" const char *concatenateStrings(const char *str1, const char *str2) {
std::string result = std::string(str1) + std::string(str2);
char *cstr = new char[result.length() + 1];
strcpy(cstr, result.c_str());
return cstr;
}
extern "C" void noRet() { printf("%s", "hello world"); }
extern "C" int *createArrayi32(const int *arr, int size) {
int *vec = (int *)malloc((size) * sizeof(int));
for (int i = 0; i < size; i++) {
vec[i] = arr[i];
}
return vec;
}
extern "C" double *createArrayDouble(const double *arr, int size) {
double *vec = (double *)malloc((size) * sizeof(double));
for (int i = 0; i < size; i++) {
vec[i] = arr[i];
}
return vec;
}
extern "C" bool return_opposite(bool input) { return !input; }
extern "C" char **createArrayString(char **arr, int size) {
char **vec = (char **)malloc((size) * sizeof(char *));
for (int i = 0; i < size; i++) {
vec[i] = arr[i];
}
return vec;
}
typedef struct Person {
const char *name;
int age;
} Person;
extern "C" const Person *getStruct(const Person *person) {
printf("Name: %s\n", person->name);
printf("Age: %d\n", person->age);
return person;
}
$ g++ -dynamiclib -o libsum.so cpp/sum.cpp
$ g++ -shared -o libsum.so cpp/sum.cpp
$ g++ -shared -o sum.dll cpp/sum.cpp
Then can use ffi-rs
invoke the dynamic library file contains functions.
const { equal } = require('assert')
const { load, RetType, ParamsType, open, close } = require('ffi-rs')
const a = 1
const b = 100
const dynamicLib = platform === 'win32' ? './sum.dll' : "./libsum.so"
open({
library: 'libsum',
path: dynamicLib
})
const r = load({
library: "libsum",
funcName: 'sum',
retType: DataType.I32,
paramsType: [DataType.I32, DataType.I32],
paramsValue: [a, b]
})
equal(r, a + b)
close('libsum')
const c = "foo"
const d = c.repeat(200)
equal(c + d, load({
library: 'libsum',
funcName: 'concatenateStrings',
retType: DataType.String,
paramsType: [DataType.String, DataType.String],
paramsValue: [c, d]
}))
equal(undefined, load({
library: 'libsum',
funcName: 'noRet',
retType: DataType.Void,
paramsType: [],
paramsValue: []
}))
equal(1.1 + 2.2, load({
library: 'libsum',
funcName: 'doubleSum',
retType: DataType.Double,
paramsType: [DataType.Double, DataType.Double],
paramsValue: [1.1, 2.2]
}))
let bigArr = new Array(100).fill(100)
equal(bigArr[0], load({
library: 'libsum',
funcName: 'createArrayi32',
retType: DataType.I32Array,
paramsType: [DataType.I32Array, DataType.I32],
paramsValue: [bigArr, bigArr.length],
retTypeLen: bigArr.length
})[0])
let bigDoubleArr = new Array(100).fill(1.1)
equal(bigDoubleArr[0], load({
library: 'libsum',
funcName: 'createArrayDouble',
retType: DataType.DoubleArray,
paramsType: [DataType.DoubleArray, DataType.I32],
paramsValue: [bigDoubleArr, bigDoubleArr.length],
retTypeLen: bigDoubleArr.length
})[0])
let stringArr = [c, c.repeat(200)]
equal(stringArr[0], load({
library: 'libsum',
funcName: 'createArrayString',
retType: DataType.StringArray,
paramsType: [DataType.StringArray, DataType.I32],
paramsValue: [stringArr, stringArr.length],
retTypeLen: stringArr.length
})[0])
const bool_val = true
equal(!bool_val, load({
library: 'libsum',
funcName: 'return_opposite',
retType: DataType.Boolean,
paramsType: [DataType.Boolean],
paramsValue: [bool_val],
}))
const person = {
name: 'tom',
age: 23,
}
const personObj = load({
library: 'libsum',
funcName: 'getStruct',
retType: {
name: DataType.String,
age: DataType.I32,
},
paramsType: [{
name: DataType.String,
age: DataType.I32,
}],
paramsValue: [person]
})
equal(person.name, personObj.name)
equal(person.age, personObj.age)