cbindgen 是一个从 Rust 库(这个库已面向暴露 C 接口进行设计)生成 C/C++ 头文件的工具。
我们在最初 Rust 生态还没起来的时候,一般都是使用 Rust 对已有的 C 库进行封装,这时,就会用到 bindgen 多一些。但是随着 Rust 生态越来越成熟,可能大量的库直接使用 Rust 实现了。这时,反而想导出 C 接口,进而供其它语言调用,这时,就会用到 cbindgen 了。
为什么这类工作,需要用到这种辅助工具呢?因为,真的很枯燥啊!!!
其实,FFI封装、转换,熟悉了之后,知识点就那些,模式也比较固定,如果接口量很大,那就需要大量重复的 coding。量一大,人手动绑定出错的机率也大。所以这种辅助工具的意义就显露出来了。基于辅助工具生成的代码,如不完美,再适当手动修一修,几下就能搞定,大大提高生产效率。
当然,如果你的 Rust 库,只是导出一两个接口,那就没必要使用这个工具了。
如何使用 cbindgen
使用 cbindgen 有两种方式:
以命令行工具的方式使用;
以库的方式在 build.rs 中使用;
两种方式各有其方便和不方便之处。第一种形式不需要写代码,但是每次 Rust 库修改升级后,可能要重新运行一次这个命令行,以生成最新的 C 头文件。第二种形式在 Rust 库编译的过程中,就自动生成了 C 头文件。
下面我们来看第一种方式。
命令行工具方式
安装 cbindgen
cargoinstall--force cbindgen
对一个暴露了 C API 的 Rust crate,直接:
cbindgen--configcbindgen.toml--cratemy_rust_library--outputmy_header.h
就可以了。 就是生成的头文件。
要注意两点:
不是任意 Rust crate 都可以,而是已经做了暴露 C API 开发的库才行。因为 cbindgen 会去扫描整个源代码,把对接的接口抽出来;
可以通过 cbindgen.toml 这个配置文件,给 cbindgen 配置行为参数,参数很多,后面有参考链接。
build.rs 方式
build.rs 的基础知识,不在这里讲了,传送门在这里:https://doc.rust-lang.org/cargo/reference/build-scripts.html。
build.rs 的功能就是在编译期间,在编译真正的 crate 之前,先编译执行 build.rs 中的代码。于是,可以在这里面做 on-fly 生成之类的工作。
按照下面这种模板使用 cbindgen 就好了:
externcratecbindgen;
usestd::env;
fnmain() {
letcrate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
cbindgen::Builder::new()
.with_crate(crate_dir)
.generate()
.expect("Unable to generate bindings")
.write_to_file("bindings.h");
}
在 build.rs 方式里,也是可以配置 cbindgen.toml 参数的。这里,编译之后,生成的 就是我们要的 C 头文件。
生成的结果看起来是什么样子?
比如,我们有一个 Rust crate:
// trebuchet.rs
#[repr(u8)]
enumAmmo{
Rock,
WaterBalloon,
Cow,
}
#[repr(C)]
structTarget{
latitude:f64,
longitude:f64,
}
// notice: #[repr(rust)]
structTrebuchet{ ... }
#[no_mangle]
unsafeextern"C"fntrebuchet_new() -> *mutTrebuchet { ... }
#[no_mangle]
unsafeextern"C"fntrebuchet_delete(treb: *mutTrebuchet) { ... }
#[no_mangle]
unsafeextern"C"fntrebuchet_fire(treb: *mutTrebuchet,
ammo: Ammo,
target: Target) { ... }
生成后的 C header 文件如下:
// trebuchet.h
#include
#include
extern"C"{
enumclassAmmo:uint8_t{
Rock =,
WaterBalloon =1,
Cow =2,
};
structTrebuchet;
structTarget{
doublelatitude;
doublelongitude;
};
voidtrebuchet_delete(Trebuchet *treb);
voidtrebuchet_fire(Trebuchet *treb, Ammo ammo, Target target);
Trebuchet*trebuchet_new();
}// extern "C"
可以看到,cbindgen 完整实现了我们的意图:
Ammo 有正确的大小和值;
Target 包含所有字段和正确的布局(字段顺序);
Trebuchet 被声明为一个 opaque 结构体;
所有的函数有了对应的声明。
补充
cbindgen 不但可以生成 C 头文件,还可以生成 C++ 的,甚至还可以生成 swift 可以用的头文件;
cbindgen 不是一包到底的,对 C 的支持相对成熟,对 C++ 的差一些。适当的时候,还是需要手动做很多事情的;
cbindgen 对 Rust 的泛型,有一定的支持;
在下一篇,我们将会使用 cbindgen 对我们之前写的库做一下生成实验
参考链接
说明文档
cbindgen.toml
announcing-cbindgen
领取专属 10元无门槛券
私享最新 技术干货