ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

如何用 Spinel 通过 FFI 直接调用 C 函数:跳过扩展编译步骤的完整教程

如何用 Spinel 通过 FFI 直接调用 C 函数:跳过扩展编译步骤的完整教程 如何用 Spinel 通过 FFI 直接调用 C 函数跳过扩展编译步骤的完整教程【免费下载链接】spinelRuby AOT compiler项目地址: https://gitcode.com/gh_mirrors/spin/spinelSpinel 是一款 Ruby AOT提前编译编译器它能把 Ruby 源码直接编译为独立的本地可执行文件。它的内置 FFI 能力是本文主角你只需要在 Ruby 源码里写几行声明AOT 编译器就会生成直连的 C 调用点——不需要 C 扩展编译、不需要mkmf、不需要require ffi就能从 Ruby 直接调用strlen、sqrt、sqlite3_open等 C 函数。本教程面向新手带你从零跑通第一个 Spinel FFI 程序。一、为什么可以跳过扩展编译步骤在传统 CRuby 中调用 C 函数通常有两条路方式流程痛点C 扩展C Extension写 C 代码 →mkmf.rb→ 编译.so→ 运行时加载每个平台都要重新编译部署麻烦ffi gem运行时dlopen libffi 间接调用动态查找符号性能有开销需系统 libffi而 Spinel 的思路完全不同声明写在 Ruby 源码里编译期就完成一切。ffi_func等声明在module体内属于编译期语法代码生成阶段src/codegen*.c 同级的 FFI 输出逻辑直接生成 C 的extern声明与调用语句spinel编译器从生成的 C 代码中刮取/* SPINEL_LINK: -lsqlite3 */这类标记注释自动附加到链接命令最终产物是一个零运行时依赖的本地二进制整个过程就是两条命令./spinel prog.rb ./prog详细说明见官方文档 docs/FFI.md。二、准备工作构建 Spinel 编译器Spinel 不从 RubyGems 分发从源码构建即可全程只需系统 C 编译器git clone https://gitcode.com/gh_mirrors/spin/spinel cd spinel make deps # 拉取 libprism 解析器一次性 make # 构建编译器 spinel 和项目工具 spin构建完成后仓库根目录会出现spinel可执行文件。它解析 Ruby → 全程序类型推断 → 生成一个 C 文件 → 调用系统cc链接成原生二进制架构原理见 README.md 的 How It Works 一节。三、快速上手5 行代码调用 libcSpinel 的 FFI 声明都放在module体内模块名就是函数的命名空间。libc和libm默认就会链接所以第一个例子连ffi_lib都不用写module LibC ffi_func :strlen, [:str], :size_t ffi_func :getpid, [], :int end puts LibC.strlen(hello, world) # 12 puts LibC.getpidffi_func的签名是ffi_func :函数名, [参数类型列表], 返回类型。仓库里有一个可直接运行的完整示例 examples/ffi/libm/libm.rb它额外声明了cos、sin、sqrt、pow四个数学函数编译运行./spinel examples/ffi/libm/libm.rb ./libm # 输出 # 1 # 4 # 1024 # 12 # pid 0: true四、ffi_func 支持哪些类型这是新手最常查的一节。类型规格type spec与 C 类型的对应关系如下摘自 docs/FFI.mdspecC 类型Spinel 侧类型:intint整数:uint32/:int32/:int16/:int8/:size_t/:long对应宽度整型整数内部统一为 int64:float/:doublefloat/double浮点:boolint布尔:strconst char *字符串NUL 结尾按strlen构造结果:binstrconst char *二进制安全字符串仅返回值可含内嵌 NUL:ptrvoid *不透明指针参数位也可传IO::Buffer:float_arrayconst double *ArrayFloat的连续存储指针:int_arrayconst int64_t *ArrayInt的连续存储指针:voidvoid仅返回位几个实用细节数组要自己传长度:int_array只给指针长度作为单独的:size_t参数传入和:strstrlen的套路一致blocking: true给耗时调用数据库 step、阻塞读、sleep加这个关键字多线程运行时会让出世界、避免 GC 屏障等待变参函数固定参数部分可以声明尾部加...但纯变参函数如printf不支持用 Spinel 内置printf代替五、链接外部库ffi_lib 与 sqlite3 实战调用系统库之外的函数用ffi_lib name声明它会变成链接行的-lname。以 SQLite3 为例需先安装libsqlite3-dev等开发包module SQL ffi_lib sqlite3 ffi_func :sqlite3_open, [:str, :ptr], :int ffi_func :sqlite3_close, [:ptr], :int ffi_func :sqlite3_errmsg, [:ptr], :str end仓库自带一个完整的 SQLite 博客系统示例帖子/标签/评论含多表 join两个文件值得精读最小 FFI 绑定层examples/ffi/sqlite/sqlite3_lib.rb演示程序examples/ffi/sqlite/blog.rb运行方式前置依赖安装步骤见 examples/ffi/sqlite/README.md./spinel examples/ffi/sqlite/blog.rb ./blog5.1 处理 C 的输出参数ffi_buffer ffi_read_ptrsqlite3_open(path, db)会写回一个数据库句柄这是 C API 最常见的模式。Spinel 用两个声明搞定ffi_buffer :db_out, 8 # 静态 8 字节缓冲整个程序生命周期存在 ffi_read_ptr :read_ptr, 0 # 从 buf 偏移 0 处读 void * rc SQL.sqlite3_open(:memory:, SQL.db_out) db SQL.read_ptr(SQL.db_out) # 取出真正的 sqlite3*如果只需要少量结构体字段ffi_read_u8/u16/u32/u64、ffi_write_*系列可以在任意偏移处读写字节且对ffi_buffer的越界访问会在编译期被拒绝。5.2 其他常用声明速查声明作用用法示例ffi_const :NAME, 值整型常量Module::NAME访问ffi_const :SQLITE_OK, 0ffi_buffer :name, 大小静态字节缓冲scratch 区 / 输出参数ffi_buffer :db_out, 8ffi_struct :Name, [[字段, 类型], ...]声明 C 结构体自动生成Name_new/Name_get_x/Name_set_x访问器布局交给 C 编译器见 docs/FFI.mdffi_callback :name, [参数], 返回声明函数指针类型可把method(:x)直接传给 C见 docs/FFI.md 中 qsort 例子ffi_source ~C ... C把一段 C 源码内嵌进编译单元单文件小适配器场景ffi_cflags ...追加头文件目录 / 链接搜索路径等编译参数库装在非标准位置时ffi_struct的小例子声明布局由目标 ABI 决定不用手写偏移module M ffi_struct :Point, [[:x, :long], [:y, :long]] end pt M.Point_new M.Point_set_x(pt, 3) puts M.Point_get_x(pt) # 3六、指针安全须知新手必看FFI 强大也危险。:ptr值不受 Spinel 垃圾回收跟踪外国内存完全由你负责记住两条铁律显式调用析构函数——没有任何人替你调sqlite3_close、sqlite3_finalize或free()传给 C 的字符串只在调用期间有效——Spinel 字符串受 GC 管理C 若把指针存起来后续 GC 可能把它释放掉需要跨调用存活就先拷进ffi_buffer另外两个小习惯:ptr为 NULL 时与nil相等判断db nil即可C 侧看不到缓冲长度长度参数务必传buf.size或由其派生的值越界读写不会被检查七、进阶运行时 FFI 的兜底方案编译期 DSL 覆盖了绝大多数场景但如果你要迁移现成的 gem——运行时才计算库路径、函数名来自数据、FFI::Struct布局等——Spinel 直接内置了ffigem 和fiddle两个兼容包require ffi加载 packages/ffi/ 打包的运行时实现extend FFI::Library、attach_function、MemoryPointer、回调等 gem 风格 API 都可用底层是 libffi dlopen需要系统 libffirequire fiddle标准库FiddleAPI同样基于同一原生层选型建议新项目优先用编译期 DSL每次调用都是直连 C 调用没有动态查表开销只有维护存量 gem 代码时才落到require ffi。八、局限性与常见问题来自官方 docs/FFI.md Limitations 一节的明确清单不支持纯变参 C 函数如printf(...)——格式化输出用 Spinel 内置printf:ptr不能进入多态值——不要塞进poly_array或泛型Hash保持为普通局部变量或包一个带ptr类型实例变量的类想完全接管链接过程静态链接、自定义库路径用-c停在 C 代码生成阶段自己驱动链接器Q编译期 DSL 和 ffi gem 能混用吗能。extend FFI::Library的模块若全是字面量声明会被编译期 DSL 直接理解只有真正需要动态能力的部分才走运行时路径。QWindows 上能用吗Spinel 目标是 POSIX 平台Windows 建议走 WSL见 README.md 的 Portability 一节。九、小结Spinel 把 Ruby 调 C 从一条需要编译扩展、链接动态库的繁琐流水线压缩成了源码里几行声明 一次 AOT 编译make deps make构建编译器在module体内写ffi_func/ffi_lib/ffi_buffer等声明./spinel prog.rb ./prog拿到零依赖的原生二进制想继续深挖按这条路径走效率最高docs/FFI.md完整 DSL 规范与类型表→ examples/ffi/libm/libm.rb最小示例→ examples/ffi/sqlite/输出参数、join 查询等真实场景。【免费下载链接】spinelRuby AOT compiler项目地址: https://gitcode.com/gh_mirrors/spin/spinel创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表