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::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::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::PathBuf; fn main() { // 參考cc 文檔 println!("cargo:rerun-if-changed=sample.c"); cc::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::default() .header("wrapper.h") .parse_callbacks(Box::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 工程的完整代碼:https://github.com/jiashiwen/wenpanrust/tree/main/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::default() .header("wrapper.h") .parse_callbacks(Box::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](https://github.com/rust-bitcoin/rust-secp256k1),我們這里只是展示一下封裝的過程。
wrapper_secp256k1 工程的完整代碼:https://github.com/jiashiwen/wenpanrust/tree/main/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
?
審核編輯:湯梓紅
評論
查看更多