乐趣区

关于后端:文盘Rust-FFI-浅尝-京东云技术团队

rust FFI 是 rust 与其余语言互调的桥梁,通过 FFI rust 能够无效继承 C 语言的历史资产。本期通过几个例子来聊聊 rust 与 C 语言交互的具体步骤。

场景一 调用 C 代码

创立工程

cargo new --bin ffi_sample



Cargo.toml 配置

[package]
name = "ffi_sample"
version = "0.1.0"
edition = "2021"
build = "build.rs"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[build-dependencies]
cc = "1.0.79"

[dependencies]
libc = "0.2.146"
libloading = "0.8.0"



编写一个简略的 c 程序 sample.c

int add(int a,int b){return a+b;}



main.rs

use std::os::raw::c_int;


#[link(name = "sample")]
extern "C" {fn add(a: c_int, b: c_int) -> c_int;
}

fn main() {let r = unsafe { add(2, 18) };
    println!("{:?}", r);
}



build.rs


fn main() {cc::Build::new().file("sample.c").compile("sample");
}




代码目录树

.
├── Cargo.lock
├── Cargo.toml
├── build.rs
├── sample.c
└── src
    └── main.rs



cargo run



场景二 应用 bindgen 通过头文件绑定 c 语言动态链接库

批改 Cargo.toml, 新增 bindgen 依赖

[package]
name = "ffi_sample"
version = "0.1.0"
edition = "2021"
build = "build.rs"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[build-dependencies]
cc = "1.0.79"
bindgen = "0.65.1"

[dependencies]
libc = "0.2.146"
libloading = "0.8.0"



新增 sample.h 头文件

#ifndef ADD_H
#define ADD_H

int add(int a, int b);

#endif



新增 wrapper.h 头文件 wrapper.h 文件将包含所有各种头文件,这些头文件蕴含咱们想要绑定的构造和函数的申明

#include "sample.h";



改写 build.rs 编译 sample.c 生成动态链接库 sample.so; 通过 bindgen 生成 rust binding c 的代码并输入到 bindings 目录

use std::path::PathBuf;

fn main() {
    // 参考 cc 文档
    println!("cargo:rerun-if-changed=sample.c");
    cc::Build::new()
        .file("sample.c")
        .shared_flag(true)
        .compile("sample.so");
    // 参考 https://doc.rust-lang.org/cargo/reference/build-scripts.html
    println!("cargo:rustc-link-lib=sample.so");
    println!("cargo:rerun-if-changed=sample.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("bindings");
    bindings
        .write_to_file(out_path.join("sample_bindings.rs"))
        .expect("Couldn't write bindings!");
}



批改 main.rs include 宏引入 sample 动态链接库的 binding。以前咱们本人手写的 C 函数绑定就不须要了,看看 bindings/sample_bindings.rs 的内容与咱们手写的函数绑定是等效的

include!("../bindings/sample_bindings.rs");

// #[link(name = "sample")]
// extern "C" {//     fn add(a: c_int, b: c_int) -> c_int;
// }

fn main() {let r = unsafe { add(2, 18) };
    println!("{:?}", r);
}




代码目录树

.
├── Cargo.lock
├── Cargo.toml
├── bindings
│   └── sample_bindings.rs
├── build.rs
├── sample.c
├── sample.h
├── src
│   └── main.rs
└── wrapper.h



ffi_sample 工程的残缺代码地位, 读者能够 clone https://github.com/jiashiwen/wenpanrust,间接运行即可

cargo run -p ffi_sample



场景三 封装一个 c 编写的库

secp256k1 是一个椭圆曲线计算的 clib,这玩意儿在密码学和隐衷计算方面的罕用算法,上面咱们从工程方面看看封装 secp256k1 如何操作

cargo new --lib wrapper_secp256k1



cargo.toml

[package]
name = "wrapper_secp256k1"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
cc = "1.0.79"
bindgen = "0.65.1"

[dependencies]



git 引入 submodule

cd wrapper_secp256k1
git submodule add https://github.com/bitcoin-core/secp256k1  wrapper_secp256k1/secp256k1_sys 



工程下新建 bindings 目录用来寄存绑定文件,该目录与 src 平级

wrapper.h

#include "secp256k1_sys/secp256k1/include/secp256k1.h"



build.rs

use std::path::PathBuf;

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("bindings");
    bindings
        .write_to_file(out_path.join("bindings.rs"))
        .expect("Couldn't write bindings!");
}



cargo build 通过

编写测试 lib.rs

include!("../bindings/secp256k1.rs");

#[cfg(test)]
mod tests {
    use super::*;

    #[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);
        }
    }
}



运行测试 cargo test 报错

warning: `wrapper_secp256k1` (lib) generated 5 warnings
error: linking with `cc` failed: exit status: 1
  |
  = 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"
  = note: ld: library not found for -lsecp256k1
          clang: error: linker command failed with exit code 1 (use -v to see invocation)


warning: `wrapper_secp256k1` (lib test) generated 5 warnings (5 duplicates)
error: could not compile `wrapper_secp256k1` (lib test) due to previous error; 5 warnings emitted



报错显示找不到编译 secp256k1 绝对应的库。

手动编译 secp256k1

cd secp256k1_sys
./autogen.sh
./configure
make
make install



编译实现后,测试通过

其实 secp256k1 有对应的 rust wrapper, 咱们这里只是展现一下封装的过程。

wrapper_secp256k1 工程的残缺代码地位, 有趣味的敌人能够 clone https://github.com/jiashiwen/wenpanrust。通过以下操作查看运行后果:

  • clone 我的项目

    git clone https://github.com/jiashiwen/wenpanrust
    cd wenpanrust
    
    
    
    
  • update submodule

    git submodule init
    git submodule update
    
    
    
    
  • 编译 secp256k1

    cd wrapper_secp256k1/secp256k1_sys 
    ./autogen.sh
    ./configure
    make
    make install  
    
    
    
    
  • run test

    cargo test -p wrapper_secp256k1
    
    
    
    

参考资料

Rust FFI (C vs Rust) 学习杂记.pdf%E5%AD%A6%E4%B9%A0%E6%9D%82%E8%AE%B0.pdf)
bindgen 官网文档
Rust FFI 编程 – bindgen 应用示例

作者:京东科技 贾世闻

起源:京东云开发者社区

退出移动版