js通过ffi调用so

本文介绍了如何在Node.js环境中使用ffi-napi库调用C语言编译的动态库(.so文件)。从安装ffi-napi到编写测试脚本,详细展示了调用无参、有参、地址传参的函数,以及设置回调函数的过程。通过实例代码解释了参数类型的对应关系,并探讨了回调函数的实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、安装ffi(依赖gcc环境)

项目开源地址:GitHub - node-ffi/node-ffi: Node.js Foreign Function Interface

1、安装:npm install node-gyp

2、安装:npm install node-ffi,如果用这个方法安装失败,则在package.json添加如下配置,然后在项目根路径执行npm install,如下图:

3、检查ffi是否安装成功,新建测试文件,ffitest.js  

const ffi = require('ffi-napi');
console.log("ffi >>> " , ffi);

4、执行命令:node ffitest.js,如果不报错,则安装成功

5、准备一个so文件,用c语言写一个程序,创建有个文件ffitest.c,编写代码如下

#include<stdio.h>
//两数相乘
int add(int a , int b)
{
        printf("add call \n");
        return a + b;
}

然后编译成so文件,编译命令:gcc -shared ffitest.c -o ffitest.so -fPIC -ldl,然后就会得到文件,ffitest.so

二、js调用so

1、调用无参函数

(1)在ffitest.c编写有个无参的函数,然后重新编译,代码如下:

char* getMsg()
{
        printf("getMsg call \n");
        return "hello world";
}

(2)编写js代码,编写文件ffitest.js,代码如下:

const ffi = require('ffi-napi');
const libs = ffi.Library("./ffitest.so" , {
        "getMsg":["string" , []]
});
const msg = libs.getMsg();
console.log("getMsg return = " , msg);

(3)执行命令:node ffitest.js,输出如下信息,说明调用成功:

 

2、调用有参值传递参数函数,以传int为例子,

(1)在ffitest.c编译一个参数类型为int的函数且重新编译,代码如下:

int add(int a , int b)
{
        printf("add call \n");
        return a + b;
}

(2)在ffitest.js,编写代码如下:

const ffi = require('ffi-napi');
const libs = ffi.Library("./ffitest.so" , {
        "getMsg":["string" , []],
        "add":["int" , ["int" , "int"]]
});
const msg = libs.getMsg();
console.log("getMsg return = " , msg);
const result = libs.add(1 , 1);
console.log("add = " , result);

(3)node ffitest.js,输出如下信息,说明调用成功

3、调用地址传参的的函数

(1)在ffitest.c中,添加一个add2,这是没有返回值,通过地址传值的方式将返回值输出,具体代码如下:

void add2(int a , int b , int* out)
{
        printf("add2 call \n");
        int c = a + b;
        *out = c;
}

(2)修改ffitest.js,代码如下:

const ffi = require('ffi-napi');
const libs = ffi.Library("./ffitest.so" , {
        "getMsg":["string" , []],
        "add":["int" , ["int" , "int"]],
        "add2":["void" , ["int" , "int" , "string"]]
});
const msg = libs.getMsg();
console.log("getMsg return = " , msg);
const result = libs.add(1 , 1);
console.log("add = " , result);
//申请4个字节,因为一个int占4个字节
//此时返回的是16进制小端模式byte类型,需要我们自己转成十进制
let out = new Buffer.alloc(4);
libs.add2(100 , 2000 , out);
const res = byte2int(out);
console.log("add2 = " , res);
process.on('exit' , function(){
  callback;
});
function byte2int(param){
        let result = param[3] << 24;
        result = result + (param[2] << 16);
        result = result + ( param[1] << 8);
        result = result +  param[0];
        return result;
}

此时返回的是byte类型,是小端模式,此时需要自己手动转int,具体怎么转可以参考上面byte2int()函数,涉及到值专递,可以参考上面的方法处理。

4、常用参数对应关系

char* ---------------- string

int -------------------- int

在处理值传递时比较麻烦,目前也有相关参数处理的方法具体请参考GitHub - TooTallNate/ref: Turn Buffer instances into "pointers",此处不做介绍

但是以上参数的基本够用了

三、回调函数的处理

在实际的项目中,往往有时候需要用到回调函数,c语言利用函数指针实现回调,但是js是可以直接把函数作为参数,相比来说js实现回调就方便得多。下面介绍如何实现调用回调函数:

(1)在ffitest.c中,编译一个回调函数,代码如下,

#include<stdio.h>
 void (*MY_CALL_BACK)(char* msg);
//两数相乘
int add(int a , int b)
{
        printf("add call \n");
        return a + b;
}
void add2(int a , int b , int* out)
{
        printf("add2 call \n");
        int c = a + b;
        *out = c;
}
char* getMsg()
{
        printf("getMsg call \n");
        return "hello world";
}
//设置回调函数
void SetCallBack(void* callback)
{
        MY_CALL_BACK = callback;
}
//执行回调函数
void testCallBacl()
{
        MY_CALL_BACK("this is a callback");
        printf("*****************\n");
}

(2)修改ffitest.js文件,在设置回到函数的时候,需要传入的是指针,参数我们可以穿pointer,具体代码如下:

const ffi = require('ffi-napi');
const libs = ffi.Library("./ffitest.so" , {
        "getMsg":["string" , []],
        "add":["int" , ["int" , "int"]],
        "add2":["void" , ["int" , "int" , "string"]],
        "SetCallBack":["void" , ["pointer"]],
        "testCallBacl":["void" , []]
});
const msg = libs.getMsg();
console.log("getMsg return = " , msg);
const result = libs.add(1 , 1);
console.log("add = " , result);
//申请4个字节,因为一个int占4个字节
//此时返回的是16进制小端模式byte类型,需要我们自己转成十进制
let out = new Buffer.alloc(4);
libs.add2(100 , 2000 , out);
const res = byte2int(out);
console.log("add2 = " , res);
//回调函数
const callback = ffi.Callback("void" , ['string'] , function(data){
        console.log("call back data : " , data);
});
//设置回调函数
libs.SetCallBack(callback);
//执行这个函数,然后会调用回调函数
libs.testCallBacl();
//byte转int
function byte2int(param){
        let result = param[3] << 24;
        result = result + (param[2] << 16);
        result = result + ( param[1] << 8);
        result = result +  param[0];
        return result;
}

四、总结,通过ffi库,可以利用c语言或者c++对程序进行扩展。这里只是对调用so的例子,调用dll代码是一样的,只是我在安装windows环境的时候被自己劝退了,如果有大神有什么解决方法可以分享一下。

 

<think>嗯,用户问的是Node.jsFFI调用结构体函数,我得先回忆一下相关知识。首先,FFI指的是Foreign Function Interface,用于调用其他语言写的库,比如C。Node.js中常用的库是node-ffi-napi,因为原来的node-ffi已经不维护了。结构体在C中很常见,但JavaScript本身没有结构体,所以需要转换。 接下来,用户可能需要知道如何定义结构体,并传递到C函数中。首先,应该用ref-struct-napi来定义结构体,比如用Struct类型。例如,定义一个Point结构体,包含x和y的int类型字段。然后,需要创建这个结构体的实例,并设置值。 然后,调用C函数的时候,参数类型要正确声明。比如,C函数接受结构体指针,那么在node-ffi-napi中要用ref.types.CString(这里可能需要纠正,正确的应该是使用指针类型,比如StructType.ptr)。或者可能需要使用ref.refType来获取指针类型。 可能还需要处理内存分配的问题,比如使用alloc和reinterpret函数。比如,用ref.alloc分配内存,然后把结构体实例写入内存,再传递给C函数。另外,返回的结构体指针需要转换成JavaScript对象,可能需要用到reinterpret,并指定偏移量和大小。 用户可能遇到的问题包括类型不匹配、内存管理错误或者结构体对齐问题。这时候需要检查结构体定义是否正确,是否和C中的声明一致,包括字段类型和顺序。此外,结构体内存对齐可能需要设置align选项,尤其是在跨平台的时候。 总结步骤应该是:安装依赖,定义结构体,声明C函数,创建实例,分配内存,调用函数,处理返回结果。同时,提醒用户注意内存管理和类型匹配,以及可能的异步调用问题。</think>以下分步介绍在 Node.js 中使用 FFI 调用 C 语言结构体函数的实现方法: 1. **安装依赖** ```bash npm install ffi-napi ref-napi ref-struct-napi ``` 2. **C语言结构体示例(libdemo.c)** ```c typedef struct { int x; int y; } Point; void printPoint(Point p) { printf("Point(%d, %d)\n", p.x, p.y); } Point* createPoint(int x, int y) { Point* p = malloc(sizeof(Point)); p->x = x; p->y = y; return p; } ``` 3. **编译动态库** ```bash gcc -shared -o libdemo.so -fPIC libdemo.c ``` 4. **Node.js 调用实现** ```javascript const ffi = require('ffi-napi'); const ref = require('ref-napi'); const Struct = require('ref-struct-napi'); // 定义结构体类型 const Point = Struct({ x: 'int', y: 'int' }); // 定义函数接口 const libdemo = ffi.Library('./libdemo', { 'printPoint': ['void', [Point]], 'createPoint': [ref.refType(Point), ['int', 'int']] }); // 创建结构体实例 const p = new Point(); p.x = 10; p.y = 20; // 调用函数 libdemo.printPoint(p); // 输出 Point(10, 20) // 处理指针返回值 const pPtr = libdemo.createPoint(30, 40); const result = pPtr.deref(); // 解引用 console.log(result.x, result.y); // 输出 30 40 ``` **关键点说明:** 1. 结构体映射 - 使用 `ref-struct-napi` 定义与C语言对应的结构体 - 字段类型必须严格匹配(int对应'int',double对应'double') 2. 内存管理 - 返回指针需使用 `deref()` 解引用 - 如C函数有 `malloc` 需自行实现 `free` 函数 3. 注意事项 - 32/64位系统需注意指针大小差异 - 结构体对齐需使用 `align` 选项处理内存对齐问题 - 复杂结构体可使用 `Union` 处理联合体 **扩展应用:** 嵌套结构体定义示例: ```javascript const Line = Struct({ start: Point, end: Point }); ``` 建议结合 `node-gyp` 编译扩展模块处理更复杂的跨语言调用场景。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AndyWei147

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值