SenRi FFI:统一跨语言外部函数接口
为什么会有这个项目
写一个需要调用原生 C 库的工具时,JavaScript 和 Python 的 FFI 方案各自为政。
Node.js 有 node-ffi-napi,Deno 有 Deno.dlopen,Bun 有内置的 ffapi,Python 有 ctypes 和 cffi。同样一个 C 函数,在 Node 里用一种写法,到 Deno 里换一种,到 Python 里再换一种。每个平台都在重复造轮子,每个开发者都在重复学一套新的 API。
SenRi FFI 要解决的就是这个问题。
它是什么
一个统一的外部函数接口库。不管你在 KossJS、Node.js、Bun、Deno 还是在 Python 里,调用 C 函数的方式是一样的。
// JS
const lib = new Library('libtest.so')
const add = lib.bind('add', 'int32', ['int32', 'int32'])
add(1, 2) // 3
# Python
lib = Library('libtest.so')
add = lib.bind('add', 'int32', ['int32', 'int32'])
add(1, 2) # 3
同样的 API,同样的类型名称,同样的调用方式。
能做什么
类型系统:int8 到 int64、float32 到 float64、pointer、cstring、void,跨语言名称一致,不需要记各平台的映射表。
结构体:自动处理布局和对齐,支持紧凑排列、嵌套、位域。定义一次,JS 和 Python 都能用。
指针:从原生内存读写基本类型和 C 字符串,支持偏移量操作。
回调:把 JS 或 Python 函数包装成 C 函数指针,传给原生库调用。
内存管理:alloc、free、addressOf,以及 errno 和 strerror 获取错误信息。
异步:KossJS 原生异步,Deno 和 Node 走 Worker 线程,Bun 有 Promise 包装(带警告)。
架构
适配器模式。每个运行时有一个对应的适配器,自动检测当前环境选择合适的那个。如果你想换底层 FFI 引擎,实现 LibraryLike 接口就能注入自定义后端,也支持部分实现配合内置适配器回退。
核心逻辑跟适配器解耦,加新运行时不难。
技术栈
JS 包用 TypeScript 6.x、Vitest 4.x,底层 FFI 引擎是 koffi。Python 包支持 3.13+,用 Hatchling 构建、Ruff 做检查,后端支持 ctypes(内置)和 cffi(可选)。测试库用 Rust 写 cdylib,统一提供给两个语言包调用。
项目结构
senri_ffi/
├── packages/
│ ├── senriffijs/ # JS/TS 包
│ │ ├── src/adapters/ # 运行时适配器
│ │ ├── src/types/ # 类型定义
│ │ ├── src/async/ # 异步支持
│ │ └── ... (library, pointer, struct, callback, memory)
│ └── senriffipy/ # Python 包
│ ├── src/senri_ffi/adapters/ # ctypes / cffi
│ └── ...
└──test-lib/ # Rust 测试库
谁适合用
- 需要在 Node、Deno、Bun、KossJS 之间共享 FFI 代码的项目
- 同一个原生库要同时给 JS 和 Python 项目调用
- 想做跨语言 SDK,不想给每个语言单独写绑定
- 对类型安全和统一 API 有要求的团队
现状
项目持续开发中。测试覆盖包括单元测试(JS 用 Vitest,Python 用 Pytest)和集成测试(通过 Rust 测试库验证跨语言一致性),CI 自动跑。
相关链接
- 项目地址:github.com/TT23XR-Studio/senri_ffi
- KossJS 文档:docss.sxxyrry.qzz.io/SenRi FFI/
- TT23XR Studio 官网:tt23xr.sxxyrry.qzz.io/
Apache-2.0 许可证,欢迎使用和贡献。
评论 (2)