Bun FFI 外部接口
Bun 提供了 FFI(Foreign Function Interface)功能,可以直接调用 C/C++/Rust 等语言编写的原生库。本章介绍 Bun FFI 的使用方法。
FFI 简介
FFI 允许 JavaScript 直接调用原生代码,无需编写 Node.js 原生插件或 WebAssembly。
优势
- 高性能:直接调用原生代码,无 IPC 开销
- 简单:无需编译绑定代码
- 灵活:可调用任何 C ABI 兼容的库
限制
- 需要了解目标库的 C API
- 内存管理需要特别注意
- 不同平台可能需要不同的库文件
基本用法
加载动态库
typescript
import { dlopen, FFIType, suffix } from "bun:ffi";
// 自动处理平台差异
// suffix: macOS -> "dylib", Linux -> "so", Windows -> "dll"
const libPath = `./libmath.${suffix}`;
const lib = dlopen(libPath, {
// 声明函数签名
add: {
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
},
});
// 调用函数
const result = lib.symbols.add(2, 3);
console.log(result); // 5系统库
typescript
import { dlopen, FFIType } from "bun:ffi";
// 加载 C 标准库
const libc = dlopen("libc.so.6", {
getpid: {
args: [],
returns: FFIType.i32,
},
getenv: {
args: [FFIType.cstring],
returns: FFIType.cstring,
},
});
console.log("PID:", libc.symbols.getpid());
console.log("HOME:", libc.symbols.getenv("HOME"));数据类型
FFIType 类型
| FFIType | C 类型 | 说明 |
|---|---|---|
bool | bool | 布尔值 |
i8 | int8_t | 8位有符号整数 |
u8 | uint8_t | 8位无符号整数 |
i16 | int16_t | 16位有符号整数 |
u16 | uint16_t | 16位无符号整数 |
i32 | int32_t | 32位有符号整数 |
u32 | uint32_t | 32位无符号整数 |
i64 | int64_t | 64位有符号整数 |
u64 | uint64_t | 64位无符号整数 |
f32 | float | 32位浮点数 |
f64 | double | 64位浮点数 |
ptr | void* | 指针 |
cstring | char* | C 字符串 |
类型示例
typescript
import { dlopen, FFIType, ptr, toArrayBuffer, toBuffer } from "bun:ffi";
const lib = dlopen("./mylib.so", {
// 整数类型
processInt: {
args: [FFIType.i32],
returns: FFIType.i32,
},
// 浮点类型
calculateFloat: {
args: [FFIType.f64, FFIType.f64],
returns: FFIType.f64,
},
// 布尔类型
checkCondition: {
args: [FFIType.bool],
returns: FFIType.bool,
},
// 字符串
processString: {
args: [FFIType.cstring],
returns: FFIType.cstring,
},
// 指针
allocateBuffer: {
args: [FFIType.u32],
returns: FFIType.ptr,
},
});指针操作
创建指针
typescript
import { ptr, toArrayBuffer, toBuffer } from "bun:ffi";
// 从 TypedArray 获取指针
const buffer = new Uint8Array([1, 2, 3, 4]);
const pointer = ptr(buffer);
console.log("指针地址:", pointer);读取指针数据
typescript
import { toArrayBuffer, toBuffer } from "bun:ffi";
// 假设 lib.symbols.getData 返回一个指针
const dataPtr = lib.symbols.getData();
// 转换为 ArrayBuffer(需要知道长度)
const arrayBuffer = toArrayBuffer(dataPtr, 0, 100); // 100 字节
// 转换为 Buffer
const buffer = toBuffer(dataPtr, 0, 100);传递缓冲区
typescript
import { ptr } from "bun:ffi";
const lib = dlopen("./mylib.so", {
processData: {
args: [FFIType.ptr, FFIType.u32],
returns: FFIType.i32,
},
});
// 创建缓冲区
const data = new Uint8Array([1, 2, 3, 4, 5]);
// 传递指针和长度
const result = lib.symbols.processData(ptr(data), data.length);字符串处理
C 字符串
typescript
import { dlopen, FFIType, CString } from "bun:ffi";
const lib = dlopen("./strlib.so", {
getString: {
args: [],
returns: FFIType.cstring,
},
setString: {
args: [FFIType.cstring],
returns: FFIType.void,
},
});
// 获取 C 字符串
const cstr = lib.symbols.getString();
console.log(cstr); // 自动转换为 JS 字符串
// 传递字符串
lib.symbols.setString("Hello from JavaScript");编码器
typescript
// 将 JS 字符串转换为 C 字符串
const encoder = new TextEncoder();
const bytes = encoder.encode("Hello\0"); // 包含 null 终止符
const lib = dlopen("./strlib.so", {
processBytes: {
args: [FFIType.ptr],
returns: FFIType.void,
},
});
lib.symbols.processBytes(ptr(bytes));结构体
传递结构体
typescript
import { ptr } from "bun:ffi";
// C 结构体:
// struct Point {
// float x;
// float y;
// };
// 创建结构体数据
const point = new Float32Array([3.14, 2.71]);
const lib = dlopen("./geometry.so", {
printPoint: {
args: [FFIType.ptr],
returns: FFIType.void,
},
});
lib.symbols.printPoint(ptr(point));结构体数组
typescript
// struct Point { float x; float y; };
// 每个 Point 8 字节
// 创建 3 个点的数组
const points = new Float32Array([
1.0, 2.0, // Point 1
3.0, 4.0, // Point 2
5.0, 6.0, // Point 3
]);
const lib = dlopen("./geometry.so", {
processPoints: {
args: [FFIType.ptr, FFIType.u32],
returns: FFIType.void,
},
});
lib.symbols.processPoints(ptr(points), 3);回调函数
JavaScript 回调
typescript
import { callback, FFIType } from "bun:ffi";
// 创建回调函数
const cb = callback({
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
}, (a, b) => {
console.log(`回调被调用: ${a} + ${b}`);
return a + b;
});
const lib = dlopen("./mylib.so", {
registerCallback: {
args: [FFIType.ptr],
returns: FFIType.void,
},
triggerCallback: {
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
},
});
// 注册回调
lib.symbols.registerCallback(cb);
// 触发回调
const result = lib.symbols.triggerCallback(10, 20);
console.log("结果:", result); // 30实际示例
调用 SQLite(不使用内置)
typescript
import { dlopen, FFIType, ptr, CString } from "bun:ffi";
const sqlite = dlopen("libsqlite3.so", {
sqlite3_open: {
args: [FFIType.cstring, FFIType.ptr],
returns: FFIType.i32,
},
sqlite3_exec: {
args: [FFIType.ptr, FFIType.cstring, FFIType.ptr, FFIType.ptr, FFIType.ptr],
returns: FFIType.i32,
},
sqlite3_close: {
args: [FFIType.ptr],
returns: FFIType.i32,
},
});
// 使用 SQLite
// ...调用系统 API
typescript
import { dlopen, FFIType } from "bun:ffi";
// macOS 示例
const foundation = dlopen("/System/Library/Frameworks/Foundation.framework/Foundation", {
// ...
});
// Linux 示例:获取系统信息
const libc = dlopen("libc.so.6", {
uname: {
args: [FFIType.ptr],
returns: FFIType.i32,
},
});调用 Rust 库
rust
// src/lib.rs
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}
#[no_mangle]
pub extern "C" fn greet(name: *const std::os::raw::c_char) -> *const std::os::raw::c_char {
let c_str = unsafe { std::ffi::CStr::from_ptr(name) };
let name = c_str.to_str().unwrap();
let greeting = format!("Hello, {}!", name);
std::ffi::CString::new(greeting).unwrap().into_raw()
}bash
# 编译 Rust 库
cargo build --releasetypescript
// 调用 Rust 库
import { dlopen, FFIType, suffix } from "bun:ffi";
const lib = dlopen(`./target/release/libmylib.${suffix}`, {
add: {
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
},
greet: {
args: [FFIType.cstring],
returns: FFIType.cstring,
},
});
console.log(lib.symbols.add(5, 3)); // 8
console.log(lib.symbols.greet("Bun")); // "Hello, Bun!"错误处理
检查返回值
typescript
const lib = dlopen("./mylib.so", {
riskyOperation: {
args: [FFIType.ptr],
returns: FFIType.i32, // 0 = 成功, -1 = 失败
},
});
const result = lib.symbols.riskyOperation(null);
if (result !== 0) {
throw new Error(`操作失败,错误码: ${result}`);
}关闭库
typescript
const lib = dlopen("./mylib.so", {
// ...
});
// 使用完毕后关闭
lib.close();性能考虑
避免频繁调用
typescript
// ❌ 不好:频繁的 FFI 调用
for (let i = 0; i < 1000000; i++) {
lib.symbols.add(i, 1);
}
// ✅ 好:批量处理
const data = new Int32Array(1000000);
lib.symbols.processArray(ptr(data), data.length);缓存函数引用
typescript
// 缓存符号引用
const { add, multiply, divide } = lib.symbols;
// 直接调用
const sum = add(1, 2);小结
本章介绍了:
- ✅ FFI 基本概念和用法
- ✅ 数据类型映射
- ✅ 指针和缓冲区操作
- ✅ 字符串处理
- ✅ 结构体传递
- ✅ 回调函数
- ✅ 调用 Rust 库示例
下一步
继续阅读 性能优化 了解 Bun 的性能调优技巧。