当我们拥有一组具有良好声明的头文件时,自己定义 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 可以是 staticdylib,默认值是动态库 dylib,有关更多详细信息,请参见 rustc --help

bindgen::Builderbindgen的主要入口点,可让为生成的绑定配置各种选项。.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。

Rust FFI 编程 - bindgen 使用示例相关推荐

  1. Rust FFI 编程 - Bindgen 工具介绍

    前面我们经历了<Rust FFI 编程 - 基础知识>.<Rust FFI 编程 - 手动绑定 C 库>和<Rust FFI 编程 - Rust 导出共享库>三个大 ...

  2. Rust FFI 编程--理解不同语言的数据类型转换

    1. 简介 "FFI"是" Foreign Function Interface"的缩写,大意为不同编程语言所写程序间的相互调用.鉴于C语言事实上是编程语言界的 ...

  3. Rust中FFI编程知识点整理总结

    Rust语言对FFI的支持 Rust 语言主要在关键字和标准库两个方面对 FFI 提供了支持,具体如下: 关键字 extern 属性 #[no_mangle] 外部块 ExternBlock 及其属性 ...

  4. rust异步编程--理解并发/多线程/回调/异步/future/promise/async/await/tokio

    1. 异步编程简介 通常我们将消息通信分成同步和异步两种: 同步就是消息的发送方要等待消息返回才能继续处理其它事情 异步就是消息的发送方不需要等待消息返回就可以处理其它事情 很显然异步允许我们同时做更 ...

  5. Rust FFI 与C语言互相调用

    Rust FFI 与C语言互相调用 参考 cbindgen 简介 二进制方式构建 脚本构建 Demo程序说明 示例工程: makefile test脚本 基本数据类型 Rust侧 C侧 对象 Rust ...

  6. python编程代码示例_python编程线性回归代码示例

    用python进行线性回归分析非常方便,有现成的库可以使用比如:numpy.linalog.lstsq例子.scipy.stats.linregress例子.pandas.ols例子等. 不过本文使用 ...

  7. Windows SDK编程之一 窗口示例程序

    /*Win32应用程序框架主要由"初始化窗口类","窗口注册类","窗口的创建"以"窗口消息函数"等组成*/ #incl ...

  8. 为什么说 Rust 是编程的未来?

    作者 | Scalac 译者 | 弯月 出品 | CSDN(ID:CSDNnews) 2020年 Stack Overflow 的调查报告显示,Rust 名列最受欢迎编程语言的榜首,86% 的开发人员 ...

  9. 《Arduino开发实战指南:机器人卷》一3.6 编程原理与示例程序

    本节书摘来华章计算机<Arduino开发实战指南:机器人卷>一书中的第3章 ,第3.6节,黄文恺 伍冯洁 陈 虹 编著更多章节内容可以访问云栖社区"华章计算机"公众号查 ...

最新文章

  1. Vue底层实现原理概述
  2. java成绩管理系统论文总结,JAVA论文成绩管理系统课程设计
  3. html加javascript和canvas类似超级玛丽游戏
  4. 动态库、静态库、运行时库、引入库之间的区别
  5. Oracle primary,unique,foreign 区别,Hibernate 关联映射
  6. 如果有人问你什么是大数据?不妨说说这10个典型的大数据案例
  7. 数据结构之优先队列:优先队列的介绍与基础操作实现,Python代码实现——14
  8. Qt笔记-Q3DScatter及QCustom3DItem的基本使用
  9. flex java 全局拦截_Flex CSS阻止底层内容
  10. self.modules() 和 self.children()的区别
  11. 在linux上安装redis
  12. 前端预览PDF总结:iframe、embed、PDFObject、PDF.js
  13. 《Objective-C 程序设计(第4版)》图书信息
  14. Java中实现银行ATM 模拟银行账户业务实现存款、取款和余额查询。
  15. 解决打开html文件为乱码(完美)
  16. 树莓派41/100- Pico控制触摸开关模块TTP223
  17. python和c++同时订阅两个话题,在一个回调函数中处理
  18. 【论文随笔2】COALA: Co-Aligned Autoencoders for Learning Semantically Enriched Audio Representations
  19. CAD矩形阵列应用与实战技巧
  20. img实现图片加载前默认图片,加载时替换真实图片,加载失败时替换加载失败图片

热门文章

  1. quic java_网络编程懒人入门(十):一泡尿的时间,快速读懂QUIC协议
  2. E.03.31 Shadowed by Pandemic, Olympic Torch Relay Begins in Japan
  3. 如何在Linux下使用虚拟光驱
  4. 转:虚幻4材质介绍 Unreal4 Material Expression
  5. c语言创建文件、文件夹、判断文件内容是否为空
  6. java sream 对Map分组排序
  7. Win11无法打开任务栏上面的日历
  8. 什么是Facebook 话题标签
  9. 快手五秒播放率什么意思?多少比较合理
  10. 代码随想录算法训练营第二十四天 | 理论基础、77. 组合