前言
rust FFI(Foreign Function Interface),即允许rust同其他语言“交互”。近期在项目开发中,由于某些原因,同一个程序的部分模块是c++写的,部分模块是rust写的,rust需要调用c++接口,并且还是异步调用。看了一圈资料,都是同步调用,于是自行摸索了一下,总结了这篇文档给有需要的人。
同步调用
网上同步调用的例子很多,这里就简单提一下,不做过多的阐述,一些详细内容可以看文末参考资料。
简单的rust call c
代码位置:github.com/kkk-imm/Rus…
// 目录结构:
/*
├── README.md
└── example01
├── Cargo.lock
├── Cargo.toml
├── libcallee.so
├── callee.c
├── src
│ └── main.rs
├── build.rs
├── target
│ ├── CACHEDIR.TAG
│ └── debug
└── callee.o
*/
// example01/src/main.rs
use std::os::raw::c_int;
#[link(name="callee")]
extern "C" {
fn sum(a: c_int, b: c_int) -> c_int;
}
fn main() {
let k = unsafe { sum(2, 4) };
println!("sum result is {}",k);
}
// example01/callee.c
// gcc -c -Wall -Werror -fpic callee.c
int sum(int a, int b) { return a + b; }
// build.rs
fn main() {
println!("cargo:rustc-link-search=native=.");
println!("cargo:rustc-link-lib=dylib=callee");
}
执行:(注意要带上 LD_LIBRARY_PATH)
LD_LIBRARY_PATH=. cargo run
异步FFI
这里所说的异步FFI是指,rust使用await语意,等待c接口返回结果。在我们项目开发过程中,rust模块位于上层,调用c接口,c接口内部会做一些耗时的IO操作。因此我们不能同步阻塞的去等待c接口返回结果。下面介绍两种实现方式。
自己封装future
假设我们的场景是,需要获取某个学生的信息,c代码对应的是存储引擎,rust侧则是查询模块。这里我们选用tokio作为rust async runtime。
代码位置:github.com/kkk-imm/Rus…
// src/main.rs
use std::future::Future;
use std::os::raw::{c_int, c_void};
use std::pin::Pin;
use std::sync::atomic::{AtomicBool, Ordering};
use std::task::{Context, Poll};
use std::thread::sleep;
use std::time;
// Record 学生信息,id、height
#[repr(C)]
#[derive(Debug, Default, Clone, PartialEq)]
pub struct Record {
id: c_int,
height: c_int,
}
// 函数参数,第一个参数实际上对应的是Record 第二个参数是个closure。第一个参数实际上是closure执行时的参数。
pub type GetRecord = unsafe extern "C" fn(*mut T, *mut c_void);
extern "C" {
pub fn query(id: c_int, fnptr: GetRecord, closure: *mut c_void);
}
// hook 与GetRecord函数签名一致。我们期望c能够传递一个record 到我们的closure中,我们处理完之后,c再free record
unsafe extern "C" fn hook(record: *mut T, closure: *mut c_void)
where
F: FnOnce(*mut T),
{
let closure = Box::from_raw(closure as *mut F); // from_raw,使得closure 可以释放内存
closure(record);
}
// get_callback 返回一个函数指针
pub fn get_callback(_closure: &F) -> GetRecord
where
F: FnOnce(*mut T),
{
hook::
}
struct QueryFuture {
query_id: c_int, // 用于传给c接口,模拟作为查询参数
state: AtomicBool, // 用于通知Future是否结束
result: Option, // 用于返回查询结果
}
impl Future for QueryFuture {
type Output = Record;
fn poll(mut self: Pin, cx: &mut Context