Rust FFI 編程 - cbindgen 工具介紹

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語言中文社區(rust-china)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。html

相關文章
相關標籤/搜索