rust FFI is a bridge for intermodulation between rust and other languages. Through FFI rust can effectively inherit the historical assets of C language. This issue uses a few examples to talk about the specific steps of rust and C language interaction.
Scenario 1 calls C code
create project
cargo new --bin ffi_sample
Cargo.toml configuration
[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"
Write a simple c program 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");
}
code directory tree
.
├── Cargo.lock
├── Cargo.toml
├── build.rs
├── sample.c
└── src
└── main.rs
cargo run
Scenario 2 Use bindgen to bind the C language dynamic link library through the header file
Modify Cargo.toml, add bindgen dependency
[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"
Add sample.h header file
int add(int a, int b);
Add wrapper.h header file
The wrapper.h file will include all the various header files that contain the declarations of the structures and functions we want to bind
Rewrite build.rs
Compile sample.c to generate dynamic link library sample.so; generate rust binding c code through bindgen and output to the bindings directory
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!");
}
Modify main.rs
The include macro introduces the binding of the sample dynamic link library. In the past, we don’t need the handwritten C function bindings. Look at the content of bindings/sample_bindings.rs and our handwritten function bindings are equivalent.
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);
}
code directory tree
.
├── Cargo.lock
├── Cargo.toml
├── bindings
│ └── sample_bindings.rs
├── build.rs
├── sample.c
├── sample.h
├── src
│ └── main.rs
└── wrapper.h
The complete code of the ffi_sample project: https://github.com/jiashiwen/wenpanrust/tree/main/ffi_sample, readers can clone https://github.com/jiashiwen/wenpanrust and run it directly
cargo run -p ffi_sample
Scenario 3 encapsulates a library written in c
secp256k1 is a clib for elliptic curve calculation, which is a common algorithm in cryptography and privacy calculation. Let's take a look at how to package secp256k1 from an engineering perspective.
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 import submodule
cd wrapper_secp256k1
git submodule add https://github.com/bitcoin-core/secp256k1 wrapper_secp256k1/secp256k1_sys
Create a new bindings directory under the project to store binding files, which is at the same level as src
wrapper.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 via
write test 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);
}
}
}
Run the test cargo test and report an error
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
The error shows that the corresponding library for compiling secp256k1 cannot be found.
Manually compile 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源创计划”,欢迎正在阅读的你也加入,一起分享。