文盘Rust -- FFI 浅尝

打印 上一主题 下一主题

主题 935|帖子 935|积分 2809

rust FFI 是rust与其他语言互调的桥梁,通过FFI rust 可以有效继承 C 语言的历史资产。本期通过几个例子来聊聊rust与C 语言交互的具体步骤。
场景一 调用C代码

创建工程
  1. cargo new --bin ffi_sample
复制代码
Cargo.toml 配置
  1. [package]
  2. name = "ffi_sample"
  3. version = "0.1.0"
  4. edition = "2021"
  5. build = "build.rs"
  6. # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
  7. [build-dependencies]
  8. cc = "1.0.79"
  9. [dependencies]
  10. libc = "0.2.146"
  11. libloading = "0.8.0"
复制代码
编写一个简单的c程序sample.c
  1. int add(int a,int b){
  2.     return a+b;
  3. }
复制代码
main.rs
  1. use std::os::raw::c_int;
  2. #[link(name = "sample")]
  3. extern "C" {
  4.     fn add(a: c_int, b: c_int) -> c_int;
  5. }
  6. fn main() {
  7.     let r = unsafe { add(2, 18) };
  8.     println!("{:?}", r);
  9. }
复制代码
build.rs
  1. fn main() {
  2.     cc::Build::new().file("sample.c").compile("sample");
  3. }
复制代码
代码目录树
  1. .
  2. ├── Cargo.lock
  3. ├── Cargo.toml
  4. ├── build.rs
  5. ├── sample.c
  6. └── src
  7.    └── main.rs
复制代码
  1. cargo run
复制代码
场景二 使用bindgen 通过头文件绑定c语言动态链接库

修改Cargo.toml,新增bindgen依赖
  1. [package]
  2. name = "ffi_sample"
  3. version = "0.1.0"
  4. edition = "2021"
  5. build = "build.rs"
  6. # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
  7. [build-dependencies]
  8. cc = "1.0.79"
  9. bindgen = "0.65.1"
  10. [dependencies]
  11. libc = "0.2.146"
  12. libloading = "0.8.0"
复制代码
新增 sample.h 头文件
  1. #ifndef ADD_H
  2. #define ADD_H
  3. int add(int a, int b);
  4. #endif
复制代码
新增 wrapper.h 头文件 wrapper.h 文件将包括所有各种头文件,这些头文件包含我们想要绑定的结构和函数的声明
  1. #include "sample.h";
复制代码
改写build.rs 编译 sample.c 生成动态链接库sample.so;通过bindgen生成rust binding c 的代码并输出到 bindings 目录
  1. use std::path::PathBuf;
  2. fn main() {
  3.     // 参考cc 文档
  4.     println!("cargo:rerun-if-changed=sample.c");
  5.     cc::Build::new()
  6.         .file("sample.c")
  7.         .shared_flag(true)
  8.         .compile("sample.so");
  9.     // 参考 https://doc.rust-lang.org/cargo/reference/build-scripts.html
  10.     println!("cargo:rustc-link-lib=sample.so");
  11.     println!("cargo:rerun-if-changed=sample.h");
  12.     let bindings = bindgen::Builder::default()
  13.         .header("wrapper.h")
  14.         .parse_callbacks(Box::new(bindgen::CargoCallbacks))
  15.         .generate()
  16.         .expect("Unable to generate bindings");
  17.     let out_path = PathBuf::from("bindings");
  18.     bindings
  19.         .write_to_file(out_path.join("sample_bindings.rs"))
  20.         .expect("Couldn't write bindings!");
  21. }
复制代码
修改main.rs include 宏引入sample 动态链接库的binding。以前我们自己手写的C函数绑定就不需要了,看看bindings/sample_bindings.rs 的内容与我们手写的函数绑定是等效的
  1. include!("../bindings/sample_bindings.rs");
  2. // #[link(name = "sample")]
  3. // extern "C" {
  4. //     fn add(a: c_int, b: c_int) -> c_int;
  5. // }
  6. fn main() {
  7.     let r = unsafe { add(2, 18) };
  8.     println!("{:?}", r);
  9. }
复制代码
代码目录树
  1. .
  2. ├── Cargo.lock
  3. ├── Cargo.toml
  4. ├── bindings
  5. │   └── sample_bindings.rs
  6. ├── build.rs
  7. ├── sample.c
  8. ├── sample.h
  9. ├── src
  10. │   └── main.rs
  11. └── wrapper.h
复制代码
ffi_sample 工程的完整代码位置,读者可以clone https://github.com/jiashiwen/wenpanrust,直接运行即可
  1. cargo run -p ffi_sample
复制代码
场景三 封装一个c编写的库

secp256k1是一个椭圆曲线计算的 clib,这玩意儿在密码学和隐私计算方面的常用算法,下面我们从工程方面看看封装secp256k1如何操作
  1. cargo new --lib wrapper_secp256k1
复制代码
cargo.toml
  1. [package]
  2. name = "wrapper_secp256k1"
  3. version = "0.1.0"
  4. edition = "2021"
  5. # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
  6. [build-dependencies]
  7. cc = "1.0.79"
  8. bindgen = "0.65.1"
  9. [dependencies]
复制代码
git 引入 submodule
  1. cd wrapper_secp256k1
  2. git submodule add https://github.com/bitcoin-core/secp256k1  wrapper_secp256k1/secp256k1_sys
复制代码
工程下新建bindings目录用来存放绑定文件,该目录与src平级
wrapper.h
  1. #include "secp256k1_sys/secp256k1/include/secp256k1.h"
复制代码
build.rs
  1. use std::path::PathBuf;
  2. fn main() {
  3.     println!("cargo:rustc-link-lib=secp256k1");
  4.     println!("cargo:rerun-if-changed=wrapper.h");
  5.     let bindings = bindgen::Builder::default()
  6.         .header("wrapper.h")
  7.         .parse_callbacks(Box::new(bindgen::CargoCallbacks))
  8.         .generate()
  9.         .expect("Unable to generate bindings");
  10.     let out_path = PathBuf::from("bindings");
  11.     bindings
  12.         .write_to_file(out_path.join("bindings.rs"))
  13.         .expect("Couldn't write bindings!");
  14. }
复制代码
cargo build 通过
编写测试 lib.rs
  1. include!("../bindings/secp256k1.rs");
  2. #[cfg(test)]
  3. mod tests {
  4.     use super::*;
  5.     #[test]
  6.     fn test_create_pubkey() {
  7.         // secp256k1返回公钥
  8.         let mut pubkey: secp256k1_pubkey = secp256k1_pubkey { data: [0; 64] };
  9.         let prikey: u8 = 1;
  10.         unsafe {
  11.             let context = secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
  12.             assert!(!context.is_null());
  13.             let ret = secp256k1_ec_pubkey_create(&*context, &mut pubkey, &prikey);
  14.             assert_eq!(ret, 1);
  15.         }
  16.     }
  17. }
复制代码
运行测试 cargo test 报错
  1. warning: `wrapper_secp256k1` (lib) generated 5 warnings
  2. error: linking with `cc` failed: exit status: 1
  3.   |
  4.   = note: LC_ALL="C" PATH="/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/bin:/Users/jiashiwen/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libobject-6d1da0e5d7930106.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libmemchr-d6d74858e37ed726.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libaddr2line-d75e66c6c1b76fdd.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libgimli-546ea342344e3761.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/librustc_demangle-8ad10e36ca13f067.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libstd_detect-0543b8486ac00cf6.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libhashbrown-7f0d42017ce08763.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libminiz_oxide-65e6b9c4725e3b7f.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libadler-131157f72607aea7.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/librustc_std_workspace_alloc-f7d15060b16c135d.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libunwind-a52bfac5ae872be2.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libcfg_if-1762d9ac100ea3e7.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/liblibc-f8e0e4708f61f3f4.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/liballoc-af9a608dd9cb26b2.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/librustc_std_workspace_core-9777023438fd3d6a.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libcore-83ca6d61eb70e9b8.rlib" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib/libcompiler_builtins-ea2ca6e1df0449b8.rlib" "-lSystem" "-lc" "-lm" "-L" "/usr/local/Cellar/rust/1.70.0/lib/rustlib/x86_64-apple-darwin/lib" "-o" "/Users/jiashiwen/rustproject/wrapper_secp256k1/target/debug/deps/wrapper_secp256k1-4bf30c62ecfdf2a7" "-Wl,-dead_strip" "-nodefaultlibs"
  5.   = note: ld: library not found for -lsecp256k1
  6.           clang: error: linker command failed with exit code 1 (use -v to see invocation)
  7. warning: `wrapper_secp256k1` (lib test) generated 5 warnings (5 duplicates)
  8. error: could not compile `wrapper_secp256k1` (lib test) due to previous error; 5 warnings emitted
复制代码
报错显示找不到编译 secp256k1 相对应的库。
手动编译secp256k1
  1. cd secp256k1_sys
  2. ./autogen.sh
  3. ./configure
  4. make
  5. make install
复制代码
编译完成后,测试通过
其实 secp256k1 有对应的 rust wrapper,我们这里只是展示一下封装的过程。
wrapper_secp256k1 工程的完整代码位置,有兴趣的朋友可以clone https://github.com/jiashiwen/wenpanrust。通过以下操作查看运行结果:

  • clone 项目
    1. git clone https://github.com/jiashiwen/wenpanrust
    2. cd wenpanrust
    复制代码
  • update submodule
    1. git submodule init
    2. git submodule update
    复制代码
  • 编译 secp256k1
    1. cd wrapper_secp256k1/secp256k1_sys
    2. ./autogen.sh
    3. ./configure
    4. make
    5. make install  
    复制代码
  • run test
    1. cargo test -p wrapper_secp256k1
    复制代码
参考资料
Rust FFI (C vs Rust)学习杂记.pdf
bindgen官方文档
Rust FFI 编程 - bindgen 使用示例
作者:京东科技 贾世闻
来源:京东云开发者社区

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

东湖之滨

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表