bun:ffi 模块,可以高效地从 JavaScript 调用本地库。它适用于支持 C ABI 的语言(Zig、Rust、C/C++、C#、Nim、Kotlin 等)。
dlopen 用法 (bun:ffi)
打印 sqlite3 的版本号:
性能
根据 我们的性能测试,bun:ffi 大约比 Node.js 通过 Node-API 使用 FFI 快 2–6 倍。
Bun 会生成并即时编译 C 绑定,能高效地在 JavaScript 类型和本地类型之间转换值。为编译 C 代码,Bun 集成了一个小巧且快速的 C 编译器 TinyCC。
用法
Zig
add.zig
terminal
dlopen:
Rust
C++
FFI 类型
支持以下FFIType 值。
FFIType | C 类型 | 别名 |
|---|---|---|
| buffer | char* | |
| cstring | char* | |
| function | (void*)(*)() | fn, callback |
| ptr | void* | pointer, void*, char* |
| i8 | int8_t | int8_t |
| i16 | int16_t | int16_t |
| i32 | int32_t | int32_t, int |
| i64 | int64_t | int64_t |
| i64_fast | int64_t | |
| u8 | uint8_t | uint8_t |
| u16 | uint16_t | uint16_t |
| u32 | uint32_t | uint32_t |
| u64 | uint64_t | uint64_t |
| u64_fast | uint64_t | |
| f32 | float | float |
| f64 | double | double |
| bool | bool | |
| char | char | |
| napi_env | napi_env | |
| napi_value | napi_value |
buffer 参数必须是 TypedArray 或 DataView。
字符串
JavaScript 字符串和 C 样式字符串不同,这使得在本地库中使用字符串变得复杂。JavaScript 字符串和 C 字符串有什么不同?
JavaScript 字符串和 C 字符串有什么不同?
JavaScript 字符串:
- UTF16(每个字符 2 字节)或者可能是 latin1,具体取决于 JavaScript 引擎和使用的字符
length单独存储- 不可变
- 通常是 UTF8(每个字符 1 字节)
- 不存储长度,而是以 null 结尾,字符串长度即为第一个遇到的
\0字符的位置 - 可变
bun:ffi 导出了 CString,它继承自 JavaScript 内置的 String,支持 null 结尾字符串,并添加了若干额外功能:
new CString() 构造函数会克隆 C 字符串,因此在释放 ptr 后继续使用 myString 是安全的。
returns 中时,FFIType.cstring 会把指针强制转换为 JavaScript string。用在 args 中时,FFIType.cstring 与 ptr 相同。
函数指针
目前不支持异步函数
CFunction。这在你使用 Bun 的 Node-API (napi) 并且已经加载了一些符号时很有用。
linkSymbols 一次性定义它们:
回调
使用JSCallback 创建可传递给 C/FFI 函数的 JavaScript 回调函数。C/FFI 函数可调用 JavaScript/TypeScript 代码。这在异步代码或想从 C 调用 JavaScript 代码时非常有用。
JSCallback 后,应调用 close() 释放内存。
实验性线程安全回调
JSCallback 支持线程安全回调(实验性)。若在不同线程中传递回调函数,就需要开启此功能。通过可选 threadsafe 参数启用。
当前,线程安全回调在从运行 JavaScript 代码的其他线程(例如 Worker)调用时效果最佳。未来版本的 Bun 会支持从任意线程调用(例如由本地库生成且 Bun 不知晓的新线程)。
⚡️ 性能提示 — 直接传递
JSCallback.prototype.ptr 比传递整个 JSCallback 对象速度略快:指针
Bun 在 JavaScript 中将指针表示为number 类型。
64 位指针如何存放在 JavaScript 数字中?
64 位指针如何存放在 JavaScript 数字中?
64 位处理器支持最多约 52 位可寻址空间。JavaScript 数字支持 53 位可用空间,多出大约 11 位。为何不用
BigInt? BigInt 性能较慢。JavaScript 引擎对 BigInt 单独分配内存,无法装进普通的 JavaScript 值。如果传入 BigInt,它会被转换成 number。TypedArray 转换为指针:
ArrayBuffer:
DataView:
read:
read 函数行为类似于 DataView,但通常更快,因为不需创建 DataView 或 ArrayBuffer。
FFIType | read 函数 |
|---|---|
| ptr | read.ptr |
| i8 | read.i8 |
| i16 | read.i16 |
| i32 | read.i32 |
| i64 | read.i64 |
| u8 | read.u8 |
| u16 | read.u16 |
| u32 | read.u32 |
| u64 | read.u64 |
| f32 | read.f32 |
| f64 | read.f64 |
内存管理
bun:ffi 不会替你管理内存。你必须在用完后释放内存。
来自 JavaScript
若想追踪TypedArray 何时不再使用,可以使用 FinalizationRegistry。
来自 C、Rust、Zig 等
若想从 C 或 FFI 端追踪TypedArray 何时不再使用,可以给 toArrayBuffer 或 toBuffer 传递一个回调和可选上下文指针。垃圾回收释放底层的 ArrayBuffer JavaScript 对象时,会调用该函数。
预期的签名与 JavaScriptCore 的 C API 一致:
内存安全
在 FFI 之外使用原始指针极不推荐。未来的 Bun 版本可能会加入 CLI 标志以禁用bun:ffi。
指针对齐
如果接口期望的是与char 或 u8 不同大小的指针,确保 TypedArray 也是对应的大小。u64* 和 [8]u8* 并不完全等价,因对齐关系不同。
传递指针
当 FFI 函数期望指针时,传入对应大小的TypedArray:
TypedArray。
高难度用法
高难度用法
如果不想自动转换,或想要
TypedArray 内特定字节偏移的指针,也可以直接获取 TypedArray 的指针: