ITPub博客

首页 > 数据库 > 数据库开发技术 > Rust FFI 编程 - cbindgen 工具介绍

Rust FFI 编程 - cbindgen 工具介绍

原创 数据库开发技术 作者:Rust语言中文社区 时间:2020-10-15 22:41:00 0 删除 编辑
cbindgen 是一个从 Rust 库(这个库已面向暴露 C 接口进行设计)生成 C/C++ 头文件的工具。
我们在最初 Rust 生态还没起来的时候,一般都是使用 Rust 对已有的 C 库进行封装,这时,就会用到 bindgen 多一些。但是随着 Rust 生态越来越成熟,可能大量的库直接使用 Rust 实现了。这时,反而想导出 C 接口,进而供其它语言调用,这时,就会用到 cbindgen 了。
为什么这类工作,需要用到这种辅助工具呢?因为,真的很枯燥啊!!!
其实,FFI封装、转换,熟悉了之后,知识点就那些,模式也比较固定,如果接口量很大,那就需要大量重复的 coding。量一大,人手动绑定出错的机率也大。所以这种辅助工具的意义就显露出来了。基于辅助工具生成的代码,如不完美,再适当手动修一修,几下就能搞定,大大提高生产效率。
当然,如果你的 Rust 库,只是导出一两个接口,那就没必要使用这个工具了。

如何使用 cbindgen

使用 cbindgen 有两种方式:
  1. 以命令行工具的方式使用;
  2. 以库的方式在 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 就是生成的头文件。
要注意两点:
  1. 不是任意 Rust crate 都可以,而是已经做了暴露 C API 开发的库才行。因为 cbindgen 会去扫描整个源代码,把对接的接口抽出来;
  2. 可以通过 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 完整实现了我们的意图:
  1. Ammo 有正确的大小和值;
  2. Target 包含所有字段和正确的布局(字段顺序);
  3. Trebuchet 被声明为一个 opaque 结构体;
  4. 所有的函数有了对应的声明。

补充

  1. cbindgen 不但可以生成 C 头文件,还可以生成 C++ 的,甚至还可以生成 swift 可以用的头文件;
  2. cbindgen 不是一包到底的,对 C 的支持相对成熟,对 C++ 的差一些。适当的时候,还是需要手动做很多事情的;
  3. cbindgen 对 Rust 的泛型,有一定的支持;
在下一篇,我们将会使用 cbindgen 对我们之前写的库做一下生成实验

参考链接

  • 说明文档
  • cbindgen.toml
  • announcing-cbindgen

原文作者:Rust语言中文社区;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2020-06-11

  • 博文量
    1769
  • 访问量
    718639