Skip to content

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 类型

FFITypeC 类型说明
boolbool布尔值
i8int8_t8位有符号整数
u8uint8_t8位无符号整数
i16int16_t16位有符号整数
u16uint16_t16位无符号整数
i32int32_t32位有符号整数
u32uint32_t32位无符号整数
i64int64_t64位有符号整数
u64uint64_t64位无符号整数
f32float32位浮点数
f64double64位浮点数
ptrvoid*指针
cstringchar*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 --release
typescript
// 调用 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 的性能调优技巧。

本站内容仅供学习和研究使用。