ffi 原生动态库标准库

ffi 原生动态库标准库

ffi 原生动态库标准库

功能

ffi 用于按本机系统 C ABI 加载 .dll.so.dylib,通过完整签名调用精确导出符号,并提供地址稳定的可写 FfiBuffer。使用前应查阅原生库的官方头文件或文档,确认函数的真实导出名称和完整原型。

BT 官方发布的 bt.exe 已包含 FFI 能力,无需安装额外版本或启用额外功能。

语法

调用过程为:

ffi.load 参数

参数类型必填默认值有效范围或可选值说明
pathString裸库名或文件路径裸库名交给系统加载器;带目录路径按当前源码目录、@ 项目根或绝对路径解析。不能为空或包含 NUL。
schemaObject0..=128必须传入严格完整签名表。显式空对象表示不允许调用任何符号。

schema 动态字段:

字段类型必填默认值有效范围或可选值说明
精确导出符号名String每个要调用的函数必填非空、无 NUL、最多 255 个 UTF-8 字节字段名大小写精确匹配;不会自动添加、删除或尝试 A / W 后缀。
字段值String最多 512 个 UTF-8 字节必须是 返回类型(参数类型, ...) 完整签名;最多 16 个参数。

支持的完整签名类型:

类型可用位置参数接受值返回值说明
void仅返回类型不允许Empty原生函数无返回值时,BT 返回 empty
i8、i16、i32、i64参数、返回Int、BoolInt按声明宽度精确检查范围;Bool 显式转为 01
u8、u16、u32、u64参数、返回非负 Int、BoolInt按声明宽度精确检查范围;u64 返回值超过 BT Int 上限时报错。
isize、usize参数、返回IntInt按当前 64 位目标检查;usize 返回值超过 BT Int 上限时报错。
f32、f64参数、返回Int、FloatFloat按声明宽度转换,不在 f32 与 f64 之间自动猜测。
ptr参数、返回null、FfiPointer、FfiBufferFfiPointer、nullFfiBuffer 以稳定基地址零拷贝传入;零地址返回 null
cstr参数、返回String、nullString、null输入编码为 UTF-8 NUL 结尾临时缓冲;返回时立即有界复制,非法 UTF-8 返回 null
wstr仅 Windows 参数、返回String、nullString、null输入编码为 UTF-16 NUL 结尾临时缓冲;返回时立即有界复制,非法 UTF-16 返回 null

ffi.load 返回值

类型说明
FfiLibrary加载成功后的动态库对象。属性访问只允许 schema 已声明的精确导出符号。

schema 会在 ffi.load() 时完整解析,但系统符号查找和 libffi 调用描述会延迟到对应函数第一次调用。首次成功后按符号缓存;重复调用不会重复解析 schema、查找符号或创建调用描述。

动态库函数参数与返回值

动态库方法的参数数量和类型必须与 schema 完全一致。所有可检测的参数数量、BT 类型、整数范围、内部 NUL、关闭状态、权限和符号缺失错误都会在原生调用前转换为中文运行时错误。

ptr 返回值规则:

原生结果BT 返回值说明
空地址null表示原生函数明确返回空指针。
非空地址FfiPointer只能判空、比较或传回 FFI;不能读取、写入、转整数或做指针算术。

FfiPointer 会继承已知 owner:返回地址落在传入 FfiBuffer 可见范围内时继承 Buffer;等于传入 FfiPointer 时继承原 Pointer;其他地址由当前动态库维持。对应 Library 或 Buffer 关闭后,Pointer 立即失效。

如果 ptr 返回值落在本次调用的临时 cstr/wstr 输入内,BT 会拒绝生成悬空 FfiPointer。cstr/wstr 返回会在临时输入释放前完成复制。

ffi.buffer 参数

参数类型必填默认值有效范围或可选值说明
sizeInt1..=BT_BYTES_LIMIT创建固定长度、零填充、基地址至少 16 字节对齐的可写内存。不能扩容。

ffi.buffer 返回值

类型说明
FfiBuffer地址稳定的 BT 托管可写 Buffer;可直接传给声明为 ptr 的参数。

FfiBuffer 方法:

方法参数返回值说明
len()Int返回脚本请求的可见字节长度。
ptr(offset = 0)offset: IntFfiPointeroffset 范围为 0..=len();末尾地址仅可作为 one-past-end 指针传递。
write(data, offset = 0)data: Bytes、offset: IntInt零拷贝读取 Bytes 并复制到 Buffer;越界报错。
to_bytes(offset = 0, length = 剩余长度)offset: Int、length: IntBytes把指定范围复制为普通共享只读 Bytes。
to_string(offset = 0)offset: IntString、null在 Buffer 可见范围内读取 NUL 结尾 UTF-8;非法 UTF-8 返回 null,无 NUL 报错。
to_wstring(offset = 0)offset: IntString、null仅 Windows;offset 必须按 2 字节对齐;非法 UTF-16 返回 null

FfiBuffer 固定上限:同时存活 256 个,实际分配总量 64 MiB。实际分配按 16 字节向上取整,BT.stats().ffi.buffer_bytes 展示实际分配量。

ffi.close 参数

参数类型必填默认值有效范围或可选值说明
valueFfiLibrary、FfiBufferBT 通过 ffi.load()ffi.buffer() 创建的资源FfiPointer 不能单独关闭。

ffi.close 返回值

类型说明
Bool第一次完成逻辑关闭并且系统卸载未报告错误时返回 true;已经关闭返回 false

关闭 Library 时会先标记资源失效,再清空 schema 和函数缓存,最后请求系统卸载。关闭 Buffer 时立即释放内存额度并使派生 Pointer 失效。操作系统可能延迟物理卸载 Library;BT 保证脚本侧失效,不承诺映像立即从进程移除。

代码示例

Windows GetSystemMetrics 完整签名调用:

让原生函数写入 FfiBuffer:

查看 FFI 资源统计:

字段类型说明
enabledBool当前构建是否启用 FFI。
open_librariesInt当前逻辑打开的动态库数量。
buffersInt当前存活的 FfiBuffer 数量。
buffer_bytesInt当前 FfiBuffer 实际分配总字节数。

Windows MessageBoxW 完整签名调用:

检查当前解释器是否启用 FFI:

注意事项

  • 当前只支持 x86_64-pc-windows-msvcx86_64-unknown-linux-gnux86_64-apple-darwinaarch64-apple-darwin,统一使用目标平台默认 C ABI。
  • 当前仍不支持 ffi.load(path) 免声明调用和 schema 返回类型提示;必须提供完整签名。结构体、联合体、可变参数、callback 和指针算术也不支持。
  • ffiBT_PERMISSION_ALLOWBT_PERMISSION_DENY 中的 ffi 能力控制,并且完全禁止在 Web 请求 VM 中加载、访问动态库属性或执行原生函数。
  • 原生调用是同步阻塞操作。CLI VM 和 App 长期 VM 可以调用,但要求 UI 主线程、COM apartment、callback、异步保存指针或原生线程回调的 API 不在支持范围内。
  • cstr/wstr 临时缓冲和 FfiBuffer 只允许同步调用。原生函数不得保存这些地址供返回后或后台线程继续访问。
  • 声明为 ptr 的返回值若落在本次调用的临时 cstr/wstr 缓冲内,调用会报错,避免生成已经失效的指针。
  • 完整签名必须与真实 C 原型完全一致。错误签名、把数据导出当函数、非法指针解引用、动态库初始化/析构或第三方函数内部缺陷都可能直接终止进程;FFI 不是沙箱,也无法捕获原生崩溃。
  • 需要崩溃隔离或超时取消的第三方库应放在独立子进程中调用。
  • cstr/wstr 返回只适用于原生文档明确保证当前调用期间可读的 NUL 结尾字符串。BT 立即复制但不释放原生地址;需要第三方函数释放的字符串必须声明为 ptr,再显式调用库提供的释放函数。
  • Windows x64 的 BOOL 应声明为 i32,句柄声明为 ptrW API 的输入字符串声明为 wstr;不要把平台相关的 C long 宽度当作固定类型。
  • 完整示例位于 examples/ffi-user32/examples/ffi-testlib/