Node FFI中结构体指针用法技术咨询
Node FFI 结构体指针用法详解
我懂你找资料时的头疼——Node FFI的结构体指针文档确实不算太直观,结合你的代码,我一步步给你讲清楚具体用法:
1. 先修正结构体定义(关键细节)
你的结构体里有个小问题:C标准的struct sockaddr_in里对应IP地址的字段是sin_addr,不是in_addr;另外sin_zero是8字节的char数组,不是单个char。先修正你的定义(需要依赖ref和ref-struct包):
const ref = require('ref'); const Struct = require('ref-struct'); // 定义 in_addr 结构体 const struct_in_addr = Struct({ 's_addr': 'ulong' // 对应C里的 in_addr_t,通常是unsigned long }); // 定义8字节char数组类型,用于sin_zero const sin_zero_array = ref.arrayType('char', 8); // 修正后的 sockaddr_in 结构体 const struct_sockaddr_in = Struct({ 'sin_family': 'short', // AF_INET 对应数值2 'sin_port': 'ushort', // 端口号,需要转网络字节序 'sin_addr': struct_in_addr, 'sin_zero': sin_zero_array // 填充用的8字节数组 });
2. 定义结构体指针类型
要传递结构体指针给C函数,首先得用ref.refType()生成对应的指针类型:
// 生成 struct sockaddr_in* 类型 const struct_sockaddr_in_ptr = ref.refType(struct_sockaddr_in);
3. 创建结构体实例并获取指针
先实例化结构体,给字段赋值(注意网络字节序转换),再通过.ref()方法获取指针:
// 创建结构体实例 const sockaddr_in = new struct_sockaddr_in(); // 赋值:AF_INET sockaddr_in.sin_family = 2; // 端口号8080,转网络字节序(大端) sockaddr_in.sin_port = ref.htons(8080); // IP地址0.0.0.0,转网络字节序 sockaddr_in.sin_addr.s_addr = ref.htonl('0.0.0.0'); // 填充sin_zero为0(可选,C里通常会 memset 为0) sockaddr_in.sin_zero.fill(0); // 获取结构体指针(struct sockaddr_in*) const sockaddr_ptr = sockaddr_in.ref();
4. 在FFI函数声明中使用指针类型
假设你的libredir里有个函数需要接收struct sockaddr_in*,比如setup_redir(struct sockaddr_in* addr, int len),那函数声明要这样写:
const ffi = require('ffi'); var redir = ffi.Library('./libredir', { // 第一个参数是struct sockaddr_in*,第二个是结构体长度 'setup_redir': ['int', [struct_sockaddr_in_ptr, 'int']] });
5. 调用函数传递指针
直接把刚才生成的指针传进去,同时传入结构体的大小(C函数通常需要知道结构体长度):
const result = redir.setup_redir(sockaddr_ptr, ref.sizeof(struct_sockaddr_in)); if (result !== 0) { console.error('Redirection setup failed'); }
6. 处理C函数返回的结构体指针
如果你的C函数会返回一个分配好的结构体指针(比如struct sockaddr_in* create_addr(char* ip, ushort port)),处理方式如下:
// 先声明函数返回类型为结构体指针 var redir = ffi.Library('./libredir', { 'create_addr': [struct_sockaddr_in_ptr, ['char*', 'ushort']], 'free_addr': ['void', [struct_sockaddr_in_ptr]] // C里必须提供释放内存的函数 }); // 调用函数获取指针 const addr_ptr = redir.create_addr('127.0.0.1', ref.htons(3000)); // 解引用指针得到结构体实例 const addr = addr_ptr.deref(); // 使用结构体数据 console.log(`IP: ${ref.ntohl(addr.sin_addr.s_addr)}, Port: ${ref.ntohs(addr.sin_port)}`); // 必须手动调用C的free函数释放内存,否则会泄漏 redir.free_addr(addr_ptr);
关键注意事项
- 字节序转换:网络协议用大端字节序,Node的字节序取决于系统,所以必须用
ref.htons()(端口)、ref.htonl()(IP)转成网络字节序;读取时用ref.ntohs()、ref.ntohl()转回来。 - 内存管理:Node创建的结构体实例内存由V8垃圾回收管理,但C函数分配的内存必须手动调用对应的free函数释放。
- 字段类型匹配:确保Node里的类型和C里的类型严格对应,比如
short对应C的short,ulong对应C的unsigned long,避免内存对齐问题。
内容的提问来源于stack exchange,提问作者PPP
相关产品推荐
相关产品推荐

