Wenpan Rust -- A Taste of FFI

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

#ifndef ADD_H#define ADD_H
int add(int a, int b);
#endif

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

#include "sample.h";

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_secp256k1git 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

#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 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); assert_eq!(ret, 1); } }}

Run the test cargo test and report an error

warning: `wrapper_secp256k1` (lib) generated 5 warningserror: 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./configuremakemake 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源创计划”,欢迎正在阅读的你也加入,一起分享。

人大毕业生盗取全校学生信息建颜值打分网站,已被刑拘 基于 NT 架构的全新 QQ Windows 版正式发布 美国将限制中国使用亚马逊、微软等提供训练 AI 模型的云服务 资金严重短缺,又一流行开源项目宣布停止功能开发 2023 年收入最高的技术岗位 LeaferJS 发布:开源、性能强悍的 2D 图形库 Visual Studio Code 1.80 发布,支持终端图片功能 Threads 注册量已破三千万,后端基于 CPython 深度“魔改” deepin 采用 Asahi Linux 适配 Apple M1 7 月数据库排行:Oracle 大涨,再度拉开比分
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/4090830/blog/10086770