本页目录

C ABI 与 bindgen

Rust 调用 C 的第一步是让类型布局一致——repr(C) 强制 Rust struct 按 C ABI 排列字段,和 C header 里的结构体逐字节对齐。bindgen 从 C header 自动生成对应的 Rust 类型和函数签名,把手工维护 Rust/C 类型映射的枯燥和出错变成自动化。callback 安全的核心是:从 C 回调 Rust 时,panic 不能跨 FFI 边界 unwind。

C ↔ Rust 类型映射

FFI 的类型对应不是自动的——必须显式使用 std::os::rawlibc crate 中的 C 兼容类型:

C typeRust type (libc)注意
intc_int (i32)
longc_long32-bit on Windows, 64-bit on Linux!
size_tusize
char * (string)*const c_charCStr::from_ptr()
void **mut c_void
struct pointer (opaque)*mut MyStruct不解引用, 只传递
enum#[repr(C)] enumC enum 是 c_int 大小

关键陷阱:long 的大小在 LLP64 (Windows) 上是 4 bytes,在 LP64 (Linux) 上是 8 bytes。如果你的 C 代码依赖 long 为 8 bytes,在 Windows 上用 i64 代替。

bindgen 自动化

bindgen 从 C header 自动生成 Rust 的外部函数声明、结构体和常量:

// build.rs
fn main() {
    println!("cargo:rerun-if-changed=wrapper.h");

    let bindings = bindgen::Builder::default()
        .header("wrapper.h")
        .allowlist_function("my_lib_.*")     // 只绑定这些函数
        .allowlist_type("MyStruct")          // 只绑定这些类型
        .generate()
        .expect("Unable to generate bindings");

    bindings.write_to_file(
        std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap())
            .join("bindings.rs"),
    ).expect("Couldn't write bindings");
}

// src/lib.rs
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

bindgen 处理:基础类型映射、struct alignment、nested structs、function pointers、conditional defines。不能处理:inline functions (需手动 port 为 Rust)、C++ templates。

Callback: C 调 Rust 的安全边界

extern "C" fn callback(x: i32) {
    println!("C called with {}", x);
}

unsafe { register_callback(callback as unsafe extern "C" fn(i32)); }

callback 中 panic 不能跨越 FFI 边界——这会产生 UB。在 Cargo.toml 中设 panic = "abort" 是免除这种风险的最直白方式。如果必须保留 unwinding,在 callback 中用 catch_unwind 包裹所有逻辑。

参考

  • bindgen: rust-lang.github.io/rust-bindgen
  • Rustonomicon: FFI
  • libc crate: docs.rs/libc

Keywords: C ABI, bindgen, c_int, c_long, c_void, callback, panic across FFI, #[repr(C)]