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
cargo install --force cbindgen
对一个暴露了 C API 的 Rust crate,直接:
cbindgen --config cbindgen.toml --crate my_rust_library --output my_header.h
就可以了。my_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 就好了:
extern crate cbindgen;
use std::env;
fn main() {
let crate_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 参数的。这里,编译之后,生成的 bindings.h
就是我们要的 C 头文件。
生成的结果看起来是什么样子?
比如,我们有一个 Rust crate:
// trebuchet.rs
#[repr(u8)]
enum Ammo {
Rock,
WaterBalloon,
Cow,
}
#[repr(C)]
struct Target {
latitude: f64,
longitude: f64,
}
// notice: #[repr(rust)]
struct Trebuchet { ... }
#[no_mangle]
unsafe extern "C" fn trebuchet_new() -> *mut Trebuchet { ... }
#[no_mangle]
unsafe extern "C" fn trebuchet_delete(treb: *mut Trebuchet) { ... }
#[no_mangle]
unsafe extern "C" fn trebuchet_fire(treb: *mut Trebuchet,
ammo: Ammo,
target: Target) { ... }
生成后的 C header 文件如下:
// trebuchet.h
#include <cstdint>
#include <cstdlib>
extern "C" {
enum class Ammo : uint8_t {
Rock = 0,
WaterBalloon = 1,
Cow = 2,
};
struct Trebuchet;
struct Target {
double latitude;
double longitude;
};
void trebuchet_delete(Trebuchet *treb);
void trebuchet_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 对我们之前写的库做一下生成实验
评论区
写评论还没有评论