bindgen 这种工具从 C 库的头文件生成 Rust FFI 绑定函数。然后,我们运行一些测试代码以验证其是否正常运行,并对它们进行调整,直到正确为止。bindgen 将 C 库中的函数公开给 Rust。我们的目标是创建一个 crate 项目,其中包含一个bindings.rs文件,该文件代表 C 库的公共 API(包括函数,结构体,枚举等),然后通过将该 crate 导入其它项目中来调用原 C 库的功能。bindgen 命令行和使用 build.rs。本文我们使用build.rs这种方式作为示例进行说明。lib<XXXX>-sys,我们本次示例,针对 C 实现的secp256k1库生成 Rust FFI 绑定。Cargo.toml,添加bindgen作为构建时的依赖项,如下所示:[build-dependencies]
bindgen = "0.55.1"
Cargo.toml文件的[build-dependencies]部分,这样就声明了对 bindgen的构建时依赖并使用了最新版本 v0.55.1,可随时通过 crates.io bindgen 页面获取最新的版本信息。build.rs文件,用来编译和链接bindgen的导出。我们可以通过 C 库的源代码,也可以直接通过链接库,本文选择通过链接库的方式。创建 wrapper.h 文件内容如下:#include <secp256k1.h>
build.rs文件内容如下:fn main() {
println!("cargo:rustc-link-lib=secp256k1");
println!("cargo:rerun-if-changed=wrapper.h");
let bindings = bindgen::Builder::default()
.header("wrapper.h")
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
rustc-link-lib = [KIND =] NAME用来指定 C 库,传递给 cargo 告知 Rust 编译器 rustc 链接 secp256k1 共享库,可选的 KIND 可以是 static,dylib,默认值是动态库 dylib,有关更多详细信息,请参见 rustc --help。bindgen::Builder是bindgen的主要入口点,可让为生成的绑定配置各种选项。.header用来指定要生成绑定的头文件。.parse_callbacks是指当更改包含的任何头文件时,生成的 crate 无效。bindings.write_to_file将绑定写入指定的文件,比如:$OUT_DIR/bindings.rs。cargo build,将立即生成与secp256k1的 Rust FFI 绑定。生成的绑定文件位于OUT_DIR/bindings.rs,其中$OUT_DIR由 cargo 根据 build.rs 确定,默认类似于./target/debug/build/crate-package-name-afc7747d7eafd720/out/。bindings.rs中有如下内容:#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct secp256k1_context_struct {
_unused: [u8; 0],
}
pub type secp256k1_context = secp256k1_context_struct;
#[repr(C)]
#[derive(Copy, Clone)]
pub struct secp256k1_pubkey {
pub data: [::std::os::raw::c_uchar; 64usize],
}
bindgen用了一个私有的大小为零的类型字段,这是其默认执行的操作。bindgen会将 C 中的const指针转换为Rust 中的 const *,并将没有修饰符的 C 指针转换为mut *。如下所示:extern "C" {
pub fn secp256k1_context_create(flags: ::std::os::raw::c_uint) -> *mut secp256k1_context;
}
extern "C" {
pub fn secp256k1_ec_pubkey_create(
ctx: *const secp256k1_context,
pubkey: *mut secp256k1_pubkey,
seckey: *const ::std::os::raw::c_uchar,
) -> ::std::os::raw::c_int;
}
include! 宏将生成的绑定直接转储到 crate 项目的入口中src/lib.rs:include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
#[test]
fn test_create_pubkey() {
// secp256k1返回公钥
let mut pubkey: secp256k1_pubkey = secp256k1_pubkey {
data: [0; 64],
};
let prikey: u8 = 1;
unsafe {
let context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
assert!(!context.is_null());
let ret = secp256k1_ec_pubkey_create(& *context, &mut pubkey, &prikey);
assert_eq!(ret, 1);
}
}
build.rs时,通过bindgen::Builder的配置方法。bindgen命令行时,通过使用其它命令行选项。bindgen生成的 Rust FFI 绑定函数,需要通过 unsafe 的方式访问 C 库中的函数,这不符合人体工程学,实际项目中,我们通常会提供一个安全的包装库。rust-secp256k1就是这样的一个包装 crate,它为libsecp256k1的所有函数提供类型安全的 Rust 绑定,Github链接:https://github.com/rust-bitcoin/rust-secp256k1。原文作者:Rust语言中文社区;如有侵权,请联系管理员删除。