FFI

8 文章

build.rs、link 和 pkg-config

4 分钟

写 FFI 时,声明 C 函数只是第一步:

unsafe extern "C" {
    fn sensor_read(out: *mut u8, len: usize) -> i32;
}

这只告诉 Rust:有一个 C ABI 函数叫 sensor_read。

但链接器还要知道:

这个函数在哪个库里?
库文件在哪个目录?
还需要链接哪些依赖库?
交叉编译时用的是目标系统的库,还是宿主机的库?

C 项目里这些通常由 Makefile、CMake、pkg-config、sysroot 和交叉工具链处理。Rust 项目里,对应工具是 #[link]、build.rs 和 build dependency。

阅读更多

静态库、动态库和 ABI

5 分钟

前面几篇讲的是函数级 FFI:extern "C"、字符串、内存归属、回调和 void *ctx。

真正交付给 C 项目时,还要回答另一个问题:

C 工程到底链接什么文件?
头文件声明哪些符号?
这些符号的 ABI 能不能长期稳定?
升级 Rust 代码后,C 侧要不要改?

C 开发者熟悉 .a、.so、.h 和 ABI。Rust 交付给 C 使用时,也要回到这些概念,而不是把 Cargo 内部模型暴露给 C。

阅读更多

FFI 回调和 void *ctx

4 分钟

C 里的回调接口通常长这样:

typedef void (*event_cb_t)(void *ctx, int event);

int device_register_callback(struct device *dev, event_cb_t cb, void *ctx);
void device_unregister_callback(struct device *dev);

这个设计很常见:函数指针负责执行,ctx 负责保存调用方环境。

Rust 内部可以用 closure 表达“代码 + 环境”。但跨 C ABI 时,捕获环境的 closure 不能直接变成 C 函数指针。C 只认识固定 ABI 的函数地址和一个 void *ctx。

阅读更多

C 字符串、内存归属和错误返回

4 分钟

写 FFI 时,extern "C" 只是入口。

真正容易出事故的地方通常是三类:

C 字符串到底是不是有效的 NUL 结尾字符串
返回的指针由谁分配、由谁释放、用哪个函数释放
错误是整数码、输出参数,还是错误文本

C 代码里这些靠接口文档和团队约定维护。Rust 可以把一部分规则包进类型,但跨语言边界本身仍然要设计清楚。

阅读更多

FFI:让 Rust 调用 C

4 分钟

Rust 调 C,表面上只是多写一段 extern "C" 声明。

真正需要处理的是另一件事:C 函数没有 Rust 的借用、生命周期、Result 和析构规则。Rust 可以调用它,但编译器无法证明这个调用是否满足 C API 的前提。

阅读更多

FFI:让 C 调用 Rust

3 分钟

让 C 调用 Rust,不是把 Rust 函数原样暴露给 C。

C 能理解的是 C ABI:符号名、调用约定、整数、指针、结构体布局、谁分配内存、谁释放内存、错误怎么返回。Rust 内部的 String、Vec、Result、trait、panic,都不应该直接跨过 C 边界。

阅读更多

unsafe:把 C 风险限制在边界

3 分钟

C 开发者第一次看到 unsafe,很容易把它理解成“Rust 里可以写 C 的地方”。

这个理解太粗。

unsafe 不是关闭 Rust 所有检查,也不是把整段代码变回 C。它只允许你做少数编译器无法静态证明安全的操作,例如解引用裸指针、调用 FFI 函数、访问可变全局变量。

阅读更多

字符串不是 char 指针

4 分钟

C 里的 char * 很强,也很模糊。

它可能表示一段可变字节,可能表示 NUL 结尾字符串,可能表示 ASCII 文本,也可能只是某个 C API 要求的参数。调用者还要自己知道:这段内存归谁释放,里面有没有 \0,编码是什么,长度从哪里来。

阅读更多