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

bindgen官方文档

Rust FFI 编程 - bindgen 使用示例

作者:京东科技 贾世闻

来源:京东云开发者社区

文盘Rust -- FFI 浅尝的更多相关文章

  1. 文盘Rust -- struct 中的生命周期

    最近在用rust 写一个redis的数据校验工具.redis-rs中具备 redis::ConnectionLike trait,借助它可以较好的来抽象校验过程.在开发中,不免要定义struct 中的 ...

  2. 文盘Rust -- 把程序作为守护进程启动

    当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望shell关闭既停服.今天我们就来聊聊这个事儿. 最早大家部署应用的通常操作是 "nohu ...

  3. 文盘Rust -- rust 连接云上数仓 starwift

    作者:京东云 贾世闻 最近想看看 rust 如何集成 clickhouse,又犯了好吃懒做的心理(不想自己建环境),刚好京东云发布了兼容ck 的云原生数仓 Starwfit,于是搞了个实例折腾一番. ...

  4. 文盘Rust -- 给程序加个日志

    作者:贾世闻 日志是应用程序的重要组成部分.无论是服务端程序还是客户端程序都需要日志做为错误输出或者业务记录.在这篇文章中,我们结合[log4rs](https://github.com/estk/l ...

  5. 文盘Rust -- 本地库引发的依赖冲突

    作者:京东科技 贾世闻 问题描述 clickhouse 的原生 rust 客户端目前比较好的有两个clickhouse-rs 和 clickhouse.rs .clickhouse-rs 是 tcp ...

  6. 文盘Rust -- 用Tokio实现简易任务池

    作者:京东科技 贾世闻 Tokio 无疑是 Rust 世界中最优秀的异步Runtime实现.非阻塞的特性带来了优异的性能,但是在实际的开发中我们往往需要在某些情况下阻塞任务来实现某些功能. 我们看看下 ...

  7. 浅尝key-value数据库(三)——MongoDB的分布式

    浅尝key-value数据库(三)——MongoDB的分布式 测试了单机MongoDB的随机读和写入性能,这一节来讲一讲MongoDB的分布式. MongoDB的分布式分成两种,一种是Replicat ...

  8. 浅尝Go语言GC

    大家好,我是小栈君,因为个人和工作的缘故,所以拖更了一点时间,但是关于拖更的内容小栈君会在后续的时间中补回来,还希望大家继续支持和关注小栈君.当然,在国内疫情稍微减缓的情况下,小栈君在这里也多说两句, ...

  9. 浅尝Spring注解开发_Servlet3.0与SpringMVC

    浅尝Spring注解开发_Servlet 3.0 与 SpringMVC 浅尝Spring注解开发,基于Spring 4.3.12 Servlet3.0新增了注解支持.异步处理,可以省去web.xml ...

  10. 浅尝ECMAScript6

    浅尝ECMAScript6 简介 ECMAScript6 是最新的ECMAScript标准,于2015年6月正式推出(所以也称为ECMAScript 2015),相比于2009年推出的es5, es6 ...

随机推荐

  1. Java对象内存管理

    对象内存管理介绍 编译好的java程序需要运行在JVM中:JVM为java程序提供并管理所需要的内存空间:"栈"."堆"."方法区"三个区域 ...

  2. API 接口主流协议有哪些?如何创建 HTTP/HTTP、WebSocket/WebSockets、TCP/UDP、gRPC、SOAP、Dubbo/HSF 等不同协议?

    API 接口协议繁多,不同的协议有着不同的使用场景.70% 互联网应用开发者日常仅会接触到最通用的 HTTP 协议,相信大家希望了解更多其他协议的信息.我们今天会给大家介绍各种 API 接口主流协议和 ...

  3. 深入理解python虚拟机:调试器实现原理与源码分析

    深入理解python虚拟机:调试器实现原理与源码分析 调试器是一个编程语言非常重要的部分,调试器是一种用于诊断和修复代码错误(或称为 bug)的工具,它允许开发者在程序执行时逐步查看和分析代码的状态和 ...

  4. 图像I、P、B帧介绍

    I.p.b 帧 I帧:帧内编码帧 :尽可能去除图像空间冗余信息来压缩传输数据量的帧内编码图像:P帧:前向预测编码帧: 通过充分将低于图像序列中前面已编码帧的时间冗余信息来压缩传输数据量的编码图像,也叫 ...

  5. 用C++编写一个简单的发布者和订阅者

    摘要:节点(Node)是通过 ROS 图进行通信的可执行进程. 本文分享自华为云社区<编写一个简单的发布者和订阅者>,作者: MAVER1CK . @[toc] 参考官方文档:Writin ...

  6. 高级Java程序员必问,Redis事务终极篇

    1. 简介 1.1 什么是Redis事务 Redis事务(Transaction)通过将多个Redis操作封装为一个原子性的操作序列,确保在事务执行过程中,不会受到其他客户端的干扰.从而在保证数据一致 ...

  7. 音视频八股文(7)-- 音频aac adts三层结构

    AAC介绍 AAC(Advanced Audio Coding)是一种现代的音频编码技术,用于数字音频的传输和存储领域.AAC是MPEG-2和MPEG-4标准中的一部分,可提供更高质量的音频数据,并且 ...

  8. vue全家桶进阶之路14:常用属性和方法

    Vue2中常用的属性和方法: 属性 el:用于指定Vue实例挂载的元素,可以是CSS选择器.HTML元素或Vue组件. data:用于存储Vue实例的响应式数据,也可以是一个函数,返回一个对象,用于提 ...

  9. Cannot apply DjangoModelPermissionsOrAnonReadOnly on a view that does not set `.queryset` or have a `.get_queryset()` method.

    这个问题是在使用 django REST 做自定义认证的时候出现的 解决方法是 在settings.py 中注释掉这个 REST_FRAMEWORK={ 'DEFAULT_PERMISSION_CLA ...

  10. 500行代码手写docker开篇-goland远程编译环境配置

    (1)500行代码手写docker开篇-goland远程编译环境配置 本系列教程主要是为了弄清楚容器化的原理,纸上得来终觉浅,绝知此事要躬行,理论始终不及动手实践来的深刻,所以这个系列会用go语言实现 ...