rust FFI является мостом для интермодуляции между rust и другими языками.Благодаря FFI rust может эффективно наследовать исторические активы языка C. В этом выпуске на нескольких примерах рассказывается о конкретных шагах взаимодействия rust и языка C.
Сценарий 1 вызывает код 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
Сценарий 2. Используйте 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
int add(int a, int b);
Добавьте заголовочный файл wrapper.h
Файл wrapper.h будет включать в себя все различные заголовочные файлы, содержащие объявления структур и функций, которые мы хотим связать.
Переписать build.rs
Скомпилируйте sample.c, чтобы сгенерировать библиотеку динамической компоновки sample.so; сгенерируйте код привязки Rust с помощью bindgen и выведите в каталог привязок.
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 вводит привязку примера библиотеки динамической компоновки. В прошлом нам не нужны были рукописные привязки функций 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, читатели могут клонировать https://github.com/jiashiwen/wenpanrust и запускать его напрямую.
cargo run -p ffi_sample
Сценарий 3 инкапсулирует библиотеку, написанную на c.
secp256k1 — это clib для расчета эллиптических кривых, который является распространенным алгоритмом в криптографии и вычислениях конфиденциальности.Давайте посмотрим, как упаковать secp256k1 с инженерной точки зрения.
cargo new --lib wrapper_secp256k1
груз.томл
[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
cd wrapper_secp256k1
git submodule add https://github.com/bitcoin-core/secp256k1 wrapper_secp256k1/secp256k1_sys
Создайте новый каталог привязок в проекте для хранения файлов привязки, который находится на том же уровне, что и src.
обертка.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!");
}
грузовая сборка через
написать тестовую 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);
1);
}
}
}
Запустите тестовый грузовой тест и сообщите об ошибке
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
参考资料:
[1] Rust FFI (C vs Rust)学习杂记.pdf:https://github.com/yujinliang/my_writing/blob/master/Rust%20FFI%20(C%20vs%20Rust)%E5%AD%A6%E4%B9%A0%E6%9D%82%E8%AE%B0.pdf
[2] bindgen官方文档:https://rust-lang.github.io/rust-bindgen/introduction.html
[3]Rust FFI 编程 - bindgen 使用示例:https://rustcc.cn/article?id=9219a366-84d3-49c8-b957-dfbade1257fc
-end-
本文分享自微信公众号 - 京东云开发者(JDT_Developers)。
如有侵权,请联系 [email protected] 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。