bun:ffi 模块可以从 JavaScript 高效地调用本地库。它可以与支持 C ABI 的语言一起使用(Zig、Rust、C/C++、C#、Nim、Kotlin 等)。
dlopen 用法 (bun:ffi)
要打印 sqlite3 的版本号:
性能
根据 我们的基准测试,bun:ffi 大约比通过 Node-API 的 Node.js FFI 快 2-6 倍。
Bun 生成并即时编译 C 绑定,高效地在 JavaScript 类型和本地类型之间转换值。为了编译 C,Bun 嵌入了 TinyCC,这是一个小型且快速的 C 编译器。
用法
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 | int644_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 以支持空终止字符串并添加了一些额外功能:
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 代码都很有用。
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 传递给函数,它将被转换为 numberWindows 注意事项: Windows API 类型 HANDLE 不表示虚拟地址,使用 ptr 将_不会_按预期工作。使用 u64 安全地表示 HANDLE 值。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 何时不再从 JavaScript 使用,您可以使用 FinalizationRegistry。
从 C、Rust、Zig 等
如果您想跟踪何时从 C 或 FFI 不再使用TypedArray,您可以将回调和可选的上下文指针传递给 toArrayBuffer 或 toBuffer。一旦垃圾收集器释放底层的 ArrayBuffer JavaScript 对象,该函数会在稍后某个时间点被调用。
预期签名与 JavaScriptCore 的 C API 相同:
内存安全
强烈不推荐在 FFI 之外使用原始指针。Bun 的未来版本可能会添加禁用bun:ffi 的 CLI 标志。
指针对齐
如果 API 期望指针大小不同于char 或 u8,请确保 TypedArray 也是该大小。由于对齐原因,u64* 与 [8]u8* 并不完全相同。
传递指针
当 FFI 函数期望指针时,传递同等大小的TypedArray:
TypedArray。
硬核模式
硬核模式
如果您不想要自动转换,或者您想要指向
TypedArray 内特定字节偏移的指针,您也可以直接获取 TypedArray 的指针: