其他
在 Rust 调用 C 库或向 C 导出函数时,用…
Rust 与 C 互操作场景:手动声明 extern "C"、自动生成绑定、导出 Rust API、写 safe wrapper 和 sys crate。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 57/100 ×0.15
适用场景
在 Rust 调用 C 库或向 C 导出特定
解决思路
- 不用 bindgen 时,在 Rust 中用 `extern "C" { ... }` 手动声明外部 C 函数(例如 `strlen`、`malloc`、`free`、`my_lib_init`、`my_lib_process`、`my_lib_cleanup`),参数和返回类型使用 `std::ffi::{c_int, c_char, c_void}` 等。
- 在安全函数内包装 unsafe C 调用:把 Rust 字符串转为 `CString`,传 `as_ptr()`,检查 C 返回码,非 0 转为 Rust 错误。
- 用 bindgen 自动生成绑定时,在 `Cargo.toml` 的 `[build-dependencies]` 添加 `bindgen = "0.70"`。
- 在 `build.rs` 中输出 Cargo 指令:`println!("cargo:rerun-if-changed=wrapper.h");`、`println!("cargo:rustc-link-lib=mylib");`、`println!("cargo:rustc-link-search=/usr/local/lib");`。
- 配置 bindgen `Builder`:用 `.header("wrapper.h")` 指定头文件,用 `.clang_arg("-I/usr/local/include")` 和 `.clang_arg("-DMYLIB_VERSION=2")` 传 clang 参数。
- 用 `.allowlist_function("mylib_.*")`、`.allowlist_type("MyLib.*")`、`.allowlist_var("MYLIB_.*")` 只生成目标库绑定,避免生成系统头文件绑定。
- 用 `.derive_debug(true)`、`.derive_default(true)` 为生成的结构体派生常用 trait。
- 用 `.blocklist_type("__va_list_tag")` 屏蔽有问题的类型。
- 调用 `.generate()` 生成绑定,把结果写入 `OUT_DIR/bindings.rs`。
- 在 `src/lib.rs` 中加 `#![allow(non_upper_case_globals)]`、`#![allow(non_camel_case_types)]`、`#![allow(non_snake_case)]`,并用 `include!(concat!(env!("OUT_DIR"), "/bindings.rs"));` 引入生成绑定。
- 采用 sys crate 结构:`mylib-sys/` 下放 `Cargo.toml`、`build.rs`、`wrapper.h`、`src/lib.rs`;安全封装 crate `mylib/` 单独放 `Cargo.toml` 和 `src/lib.rs`。
- 在 `mylib-sys/Cargo.toml` 设置 `links = "mylib"` 告诉 Cargo 该 crate 链接 libmylib;`[build-dependencies]` 添加 `bindgen = "0.70"`、`pkg-config = "0.3"` 用于系统库探测。
- 在 `mylib-sys/build.rs` 先尝试 `pkg_config::probe_library("mylib")`,成功后遍历 `lib.include_paths` 输出 `cargo:include=...` 并返回。
- 若 pkg-config 失败,用 `cc::Build::new().file("vendor/mylib/src/mylib.c").include("vendor/mylib/include").compile("mylib");` 从 vendored 源码编译,并输出 `cargo:rerun-if-changed=vendor/mylib/src/mylib.c`。
- 写 safe wrapper:定义结构体持有原始 C 句柄 `*mut ffi::mylib_t`;按线程安全假设实现 `unsafe impl Send for MyLib {}` 和 `unsafe impl Sync for MyLib {}`。
- 在 `MyLib::new` 中用 `CString::new(config)`,调用 `unsafe { ffi::mylib_create(c_config.as_ptr()) }`,检查 `handle.is_null()`,失败返回 `Error::InitFailed`。
- 在 `process` 方法中调用 `unsafe { ffi::mylib_process(self.handle, data.as_ptr(), data.len()) }`,返回负数则报错,否则返回 `Ok(result as usize)`。
- 为 `MyLib` 实现 `Drop`,在 `drop` 中调用 `unsafe { ffi::mylib_destroy(self.handle) };` 释放 C 资源。
- 用 cbindgen 导出 Rust 到 C:在 `Cargo.toml` 的 `[build-dependencies]` 添加 `cbindgen = "0.27"`。
- 导出函数用 `#[no_mangle] pub extern "C" fn ...`;例如 `mylib_create` 用 `Box::into_raw(Box::new(instance))` 返回 `*mut MyLib`。
- `mylib_destroy` 接收 `*mut MyLib`,检查非空后用 `unsafe { drop(Box::from_raw(ptr)) };` 释放。
- `mylib_process` 接收 `ptr`、`data`、`len`,用 `unsafe { &mut *ptr }` 和 `unsafe { std::slice::from_raw_parts(data, len) }` 构造切片,调用 `lib.process(...)`,成功返回 `n as std::ffi::c_int`,失败返回 `-1`。
- 在 `build.rs` 中用 cbindgen:读取 `CARGO_MANIFEST_DIR`,调用 `cbindgen::Builder::new().with_crate(crate_dir).with_language(cbindgen::Language::C).generate()`,写入 `include/mylib.h`。
- 在 `build.rs` 链接静态库:`println!("cargo:rustc-link-lib=static=mylib");` 和 `println!("cargo:rustc-link-search=native=/path/to/lib");`。
- 在 `build.rs` 链接动态库:`println!("cargo:rustc-link-lib=dylib=mylib");`。
- 在 macOS 链接 Framework:`println!("cargo:rustc-link-lib=framework=CoreFoundation");`。
- 用 cc crate 构建 C 源码:`cc::Build::new().file("src/helper.c").flag("-std=c11").compile("helper");`。
适用边界 · 注意事项
- 不需要与 C ABI 互操作,或目标环境没有 C 编译器、libclang、pkg-config 等工具链时,不适合使用 bindgen/cbindgen 这套流程。
- 无法接受 unsafe、手动管理内存与所有权,或必须完全避免 C 依赖时,应避免直接 FFI。
- 已有成熟安全绑定或 sys crate 时,不必自己从零生成绑定和写 wrapper。