Rust FFI 编程 - bindgen 使用示例
当我们拥有一组具有良好声明的头文件时,自己定义 C 库的 Rust FFI 绑定函数是毫无意义的。我们可以使用 bindgen
这种工具从 C 库的头文件生成 Rust FFI 绑定函数。然后,我们运行一些测试代码以验证其是否正常运行,并对它们进行调整,直到正确为止。
本文我们将通过一个示例,讨论如何使用 bindgen
将 C 库中的函数公开给 Rust。我们的目标是创建一个 crate 项目,其中包含一个bindings.rs
文件,该文件代表 C 库的公共 API(包括函数,结构体,枚举等),然后通过将该 crate 导入其它项目中来调用原 C 库的功能。
上一篇我们介绍了使用 bindgen 为 C 库创建 Rust FFI 绑定有两种方式:使用 bindgen
命令行和使用 build.rs
。本文我们使用build.rs
这种方式作为示例进行说明。
1. 设置 crate 项目
一般 Rust FFI 绑定的 crate 项目会包含构建和导出 C 库的 unsafe 函数, crate 的 Rust 标准命名约定为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 页面获取最新的版本信息。
其次在 crate 项目的根目录下创建一个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
。
2. 生成绑定
现在直接运行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],
}
由于 Rust 与 C 不同,不允许对结构体进行单独的声明和定义。我们可以看到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;
}
3. 使用生成的绑定,测试
我们可以使用include!
宏将生成的绑定直接转储到 crate 项目的入口中src/lib.rs
:
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
然后,我们可以编写测试,以验证生成的 Rust FFI 是否可以正常工作:
#[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);
}
}
完整代码:https://github.com/lesterli/rust-practice/tree/master/ffi/secp256k1-sys
自定义生成的绑定
如果生成的绑定,我们可以通过以下几种方式对结构体,枚举等进行调整:
- 使用
build.rs
时,通过bindgen::Builder
的配置方法。 - 使用
bindgen
命令行时,通过使用其它命令行选项。 - 也可以直接在C/C++源代码中添加注释。
具体可以参考:https://rust-lang.github.io/rust-bindgen/
与此同时,直接使用bindgen
生成的 Rust FFI 绑定函数,需要通过 unsafe
的方式访问 C 库中的函数,这不符合人体工程学,实际项目中,我们通常会提供一个安全的包装库。rust-secp256k1
就是这样的一个包装 crate,它为libsecp256k1
的所有函数提供类型安全的 Rust 绑定,Github链接:https://github.com/rust-bitcoin/rust-secp256k1。
- (五)如何编写高性能日志
- Scala之偏函数Partial Function
- (六)关于网络编程的一些实用技巧和细节
- 快学Scala习题答案汇总
- (八)高性能服务器架构设计总结1——以flamigo服务器代码为例
- (八)高性能服务器架构设计总结2——以flamigo服务器代码为例
- Scala集合练习题
- 机器学习(10)之趣味案例理解朴素贝叶斯
- Spart DataSet数据集
- (八)高性能服务器架构设计总结3——以flamigo服务器代码为例
- (八)高性能服务器架构设计总结4——以flamigo服务器代码为例
- SQL员工部门表综合查询60题
- 如何对Scala中集合(Collections)进行排序
- 小白教程——安装和使用PyCharm
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Web渗透测试|SQL报错注入
- Python|运算符的运用
- Windows下登录凭证窃取技巧
- 小浩发现这篇浮点数的文章讲的真不错!
- 安全攻击溯源思路及案例
- 图解:「归并排序」
- STM32CubeMX6.0 + HAL + LittleVGL7.6 等学习[最全附工程源码]
- 国庆期间,我造了台计算机
- 编程语言的 IDE 支持
- 独家 | 手把手教你用Python的Prophet库进行时间序列预测
- 【Git】:基础的基础
- windows解决SpringBoot启动时:APPLICATION FAILED TO START
- 【Git】:基础操作篇
- 低光照图像增强算法汇总
- 用Python解决女朋友看电影没字幕的需求