Rust FFI 与C语言互相调用
参考
https://cloud.tencent.com/developer/article/2077534
https://github.com/shepmaster/rust-ffi-omnibus
cbindgen 简介
二进制方式构建
$ cargo install cbindgen
//默认构建C++头文件 C语言需要 --lang C
$ cd /path/to/my/project && cbindgen . -o target/my_project.h
使用配置构建参考:https://github.com/eqrion/cbindgen/blob/master/docs.md
脚本构建
Cargo.toml
[package]
...
build="build.rs"[build-dependencies]
...
cbindgen = "x.x.x"
build.rs 与 Cargo.toml同级目录,自用的build.rs配置
extern crate cbindgen;use cbindgen::RenameRule::CamelCase;
use cbindgen::{StructConfig, ParseConfig, SortKey::Name};use std::env;
use std::path::PathBuf;
use cbindgen::Config;
use std::vec;fn main() {let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();let package_name = env::var("CARGO_PKG_NAME").unwrap();let output_file = target_dir().join(format!("{}.hpp", package_name)).display().to_string();let structure = StructConfig{rename_fields : CamelCase,..Default::default()};let parse = ParseConfig {parse_deps: true,include: Some(vec![String::from("reqwest::blocking")]),..Default::default()};let config = Config {namespace: Some(String::from("ffi")),includes: vec![String::from("ffi.hpp")],pragma_once: true,cpp_compat:true,sort_by: Name,structure,parse,..Default::default()};cbindgen::generate_with_config(&crate_dir, config).unwrap().write_to_file(&output_file);
}/// Find the location of the `target/` directory. Note that this may be
/// overridden by `cmake`, so we also need to check the `CARGO_TARGET_DIR`
/// variable.
fn target_dir() -> PathBuf {if let Ok(target) = env::var("CARGO_TARGET_DIR") {PathBuf::from(target)} else {PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()).join("target")}
}
Demo程序说明
Cargo.toml 可能需要
[dependencies]
libc = "0.2.139"[lib]
crate_type = ["cdylib"]
name = "demo"
cargo build --release
make
./test_c
makefile
all:demodemo:main.c#或者在这加入cargo build --releasegcc -o demo main.c -I/usr/include -L./target/release -ldemoclean:rm demo
test脚本
chmod +x test_c
export LD_LIBRARY_PATH=$PWD/target/release:$LD_LIBRARY_PATH./demo
基本数据类型
Rust侧
#[no_mangle]
pub extern "C" fn addition(a: u32, b: u32) -> u32 {a + b
}
C侧
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>extern uint32_t
addition(uint32_t, uint32_t);int main(void) {uint32_t sum = addition(1, 2);printf("%" PRIu32 "\n", sum);
}
对象
这里的对象传递是通过在堆上建立Rust风格的struct内存区,传递给C的是对象的指针,C中保存指针,使用时传入Rust,Rust根据struct信息操作内存。
后面元组部分展示了#[repr©]的方式,直接建立C风格的struct内存区,C中可以直接操作struct内的内存。
Rust侧
extern crate libc;use libc::c_char;
use std::collections::HashMap;
use std::ffi::CStr;pub struct ZipCodeDatabase {population: HashMap<String, u32>,
}impl ZipCodeDatabase {fn new() -> ZipCodeDatabase {ZipCodeDatabase {population: HashMap::new(),}}fn populate(&mut self) {for i in 0..100_000 {let zip = format!("{:05}", i);self.population.insert(zip, i);}}fn population_of(&self, zip: &str) -> u32 {self.population.get(zip).cloned().unwrap_or(0)}
}#[no_mangle]
pub extern "C" fn zip_code_database_new() -> *mut ZipCodeDatabase {Box::into_raw(Box::new(ZipCodeDatabase::new()))
}#[no_mangle]
pub extern "C" fn zip_code_database_free(ptr: *mut ZipCodeDatabase) {if ptr.is_null() {return;}unsafe {Box::from_raw(ptr);}
}#[no_mangle]
pub extern "C" fn zip_code_database_populate(ptr: *mut ZipCodeDatabase) {let database = unsafe {assert!(!ptr.is_null());&mut *ptr};database.populate();
}#[no_mangle]
pub extern "C" fn zip_code_database_population_of(ptr: *const ZipCodeDatabase,zip: *const c_char,
) -> u32 {let database = unsafe {assert!(!ptr.is_null());&*ptr};let zip = unsafe {assert!(!zip.is_null());CStr::from_ptr(zip)};let zip_str = zip.to_str().unwrap();database.population_of(zip_str)
}
C侧
c++ 最好使用包装类,在析构中free对象
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>typedef struct zip_code_database zip_code_database_t;extern zip_code_database_t *
zip_code_database_new(void);extern void
zip_code_database_free(zip_code_database_t *);extern void
zip_code_database_populate(zip_code_database_t *);extern uint32_t
zip_code_database_population_of(const zip_code_database_t *, const char *zip);int main(void) {zip_code_database_t *database = zip_code_database_new();zip_code_database_populate(database);uint32_t pop1 = zip_code_database_population_of(database, "90210");uint32_t pop2 = zip_code_database_population_of(database, "20500");zip_code_database_free(database);printf("%" PRId32 "\n", (int32_t)pop1 - (int32_t)pop2);
}
slice
Rust侧
extern crate libc;use libc::size_t;
use std::slice;#[no_mangle]
pub extern "C" fn sum_of_even(n: *const u32, len: size_t) -> u32 {let numbers = unsafe {assert!(!n.is_null());slice::from_raw_parts(n, len as usize)};numbers.iter().filter(|&v| v % 2 == 0).sum()
}
C侧
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>extern uint32_t
sum_of_even(const uint32_t *numbers, size_t length);int main(void) {uint32_t numbers[] = {1, 2, 3, 4, 5, 6};size_t length = sizeof numbers / sizeof *numbers;uint32_t sum = sum_of_even(numbers, length);printf("%" PRIu32 "\n", sum);
}
字符串C传入
不复制数据
Rust侧
入参 *const c_char
extern crate libc;use libc::c_char;
use std::ffi::CStr;#[no_mangle]
pub extern "C" fn print_c_string(s: *const c_char) {let c_str = unsafe {assert!(!s.is_null());CStr::from_ptr(s)};let str = c_str.to_str().unwrap();println!("printed from rust: {:#?}",str);
}
C侧
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>extern uint32_t
print_c_string(const char *str);int main(void) {print_c_string("göes to élevên");
}
复制数据
在to_owned时复制了数据,不怕C侧free掉原来的内存
Rust侧
extern crate libc;use libc::c_char;
use std::ffi::CStr;#[no_mangle]
pub extern "C" fn print_c_string(s: *const c_char) {let c_str = unsafe {assert!(!s.is_null());CStr::from_ptr(s)};let r_str = c_str.to_str().unwrap().to_owned();println!("printed from rust: {:#?}",r_str);
}
C侧
与前面一样
字符串Rust传出
C侧需要释放的方式
不推荐这几种方式,很容易忘记释放导致内存泄漏。
1:提供专门的函数
普通方式在C侧要主动销毁,推荐采用RAII方式。
Rust侧
extern crate libc;use libc::c_char;
use std::ffi::CString;
use std::iter;#[no_mangle]
pub extern "C" fn theme_song_generate(length: u8) -> *mut c_char {let mut song = String::from("💣 ");song.extend(iter::repeat("na ").take(length as usize));song.push_str("Batman! 💣");let c_str_song = CString::new(song).unwrap();c_str_song.into_raw()
}#[no_mangle]
pub extern "C" fn theme_song_free(s: *mut c_char) {unsafe {if s.is_null() {return;}drop(CString::from_raw(s));};
}
C侧
#include <stdio.h>
#include <stdint.h>extern char *
theme_song_generate(uint8_t length);extern void
theme_song_free(char *);int main(void) {char *song = theme_song_generate(5);printf("%s\n", song);theme_song_free(song);
}
2:malloc函数传入rust
rust侧
use libc::{c_char, c_void};
use std::ffi::CString;
use std::usize;const STRING : &str= "hello markrenChina";type Allocator = unsafe extern fn(usize) -> *mut c_void;#[no_mangle]
pub unsafe extern fn get_string_with_allocator(allocator: Allocator) -> *mut c_char {let ptr: *mut c_char = allocator(STRING.as_bytes().len()).cast();copy_string(ptr);ptr
}///这里展示与缓冲区方式不一样的函数copy api
#[no_mangle]
pub unsafe extern fn copy_string(ptr: *mut c_char) {let bytes = STRING.as_bytes();let len = bytes.len();std::ptr::copy(STRING.as_bytes().as_ptr().cast(), ptr, len);std::ptr::write(ptr.offset(len as isize) as *mut u8, 0u8);
}#[no_mangle]
pub unsafe extern fn free_string(ptr: *const c_char) {let _ = CString::from_raw(ptr as *mut _);
}
C侧
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>typedef void *(*Allocator)(uintptr_t);char *get_string_with_allocator(Allocator allocator);/***这里展示与缓冲区方式不一样的函数copy api*/
void copy_string(char *ptr);void free_string(const char *ptr);int main() {char* rust_string = get_string_with_allocator(malloc);printf("%s\n",rust_string);free(rust_string); //This use free not free_string
}
3:Rust调用glibc
具体见示例工程
Rust侧
#[no_mangle]
pub unsafe extern fn get_string_with_malloc() -> *mut c_char{let ptr: *mut c_char = libc::malloc(get_string_len()).cast();copy_string(ptr);ptr
}
C侧
int main() {char* rust_string = get_string_with_malloc();printf("%s\n",rust_string);free(rust_string); //This use free not free_string
}
回调的方式
回调方式不需要C侧主动释放,因为是借用Rust的字符串,但是可能需要注意线程。
Rust侧
use std::ffi::*;type Callback = unsafe extern fn(*const c_char);#[no_mangle]
pub unsafe extern "C" fn rust_call_c(callback: Callback ){let c_string = CString::new("su").expect("CString new failed");callback(c_string.as_ptr())
}
C侧
#include <stdio.h>typedef void (*Callback)(const char*);void rust_call_c(Callback callback);void callback(const char* string) {printf("printed from C: %s \n", string);
}int main() {rust_call_c(callback);return 0;
}
C传入缓冲区方式
windows系统调用常见的方式。
不同上面的C侧主动释放的方式,传入缓冲区的方式是,C侧申请(malloc)C侧释放(free)的方式是常规的代码操作。
c++可以通过包装,传出std::string,析构释放。
Rust侧
use libc::{c_char, c_int };
use std::{slice, ptr ,usize};const HELLO: &str = "hello worls";#[no_mangle]
pub unsafe extern "C" fn get_string_api(buffer: *mut c_char, length: *mut usize) -> c_int {if buffer.is_null() {if length.is_null(){return -1;}else {*length = HELLO.asbytes().len() + 1;return 0;}}let buffer = slice::from_raw_parts_mut(buffer as *mut u8, *length);if HELLO.len() >= buffer.len() {return -1;}ptr::copy_nonoverlapping(HELLO.as_ptr(),buffer.as_mut_ptr(),HELLO.len(),);buffer[HELLO.len()] = 0;HELLO.len() as c_int
}
C侧
#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>/**仿windows系统函数的方式 * if buffer NULL @return need len* @retrun -1 fail*/
int get_string_api(char *buffer, uintptr_t *length);int main(){size_t* len;get_string_api(NULL,len);printf("len = %ld\n", *len);char * buff = malloc((*len) * sizeof(char ));get_string_api(buff,len);printf("string = %s\n", buff);free(buff);
}
元组
#[repr©]这种以C语言方式的结构体内存布局方式,能被大部分高级语言解析并使用。
Rust侧
use std::convert::From;// A Rust function that accepts a tuple
fn flip_things_around_rust(tup: (u32, u32)) -> (u32, u32) {let (a, b) = tup;(b + 1, a - 1)
}// A struct that can be passed between C and Rust
#[repr(C)]
pub struct Tuple {x: u32,y: u32,
}// Conversion functions
impl From<(u32, u32)> for Tuple {fn from(tup: (u32, u32)) -> Tuple {Tuple { x: tup.0, y: tup.1 }}
}impl From<Tuple> for (u32, u32) {fn from(tup: Tuple) -> (u32, u32) {(tup.x, tup.y)}
}// The exported C method
#[no_mangle]
pub extern "C" fn flip_things_around(tup: Tuple) -> Tuple {flip_things_around_rust(tup.into()).into()
}
C侧
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>typedef struct {uint32_t x;uint32_t y;
} tuple_t;extern tuple_t
flip_things_around(tuple_t);int main(void) {tuple_t initial = { .x = 10, .y = 20 };tuple_t result = flip_things_around(initial);printf("(%" PRIu32 ",%" PRIu32 ")\n", result.x, result.y);
}
vector给C
Rust侧
extern crate libc;use libc::size_t;
use std::mem;#[no_mangle]
pub extern "C" fn counter_generate(size: size_t, vec: *mut *mut i16) -> size_t {let mut counted: Vec<_> = (0..).take(size).collect();counted.shrink_to_fit();let ret = counted.len();unsafe { *vec = counted.as_mut_ptr() };mem::forget(counted);ret
}#[no_mangle]
pub extern "C" fn counter_free(arr: *mut i16, size: size_t) {unsafe {if arr.is_null() {return;}Vec::from_raw_parts(arr, size, size)};
}
C侧
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>extern size_t
counter_generate(size_t size, int16_t **vec);extern void
counter_free(int16_t *vec, size_t size);int main(void) {int16_t *vec;size_t vec_len = counter_generate(10, &vec);for (size_t i = 0; i < vec_len; i++) {printf("%" PRId16 "..", vec[i]);}printf("\n");counter_free(vec, vec_len);
}
CMakeList.txt配置Rust编译
简单介绍一下,使用时,在根目录下新建build文件夹,编译输出在这个文件夹下
cd build && cmake.. && make
假设我们的rust 模块叫 rustlib
父CMakeList.txt
add_subdirectory(rustlib)
rustlib目录下CMakeList.txt
# cargo 设置
if (CMAKE_BUILD_TYPE STREQUAL "Debug")set(CARGO_CMD cargo build)set(TARGET_DIR "debug")
else ()set(CARGO_CMD cargo build --release)set(TARGET_DIR "release")
endif ()set(RUSTLIB_SO "${CMAKE_CURRENT_BINARY_DIR}/${TARGET_DIR}/librustlib.so")add_custom_target(rustlib ALLCOMMENT "Compiling rustlib module"COMMAND CARGO_TARGET_DIR=${CMAKE_CURRENT_BINARY_DIR} ${CARGO_CMD}COMMAND cp ${RUSTLIB_SO} ${CMAKE_CURRENT_BINARY_DIR}WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set_target_properties(rustlib PROPERTIES LOCATION ${CMAKE_CURRENT_BINARY_DIR})add_test(NAME rustlib_testCOMMAND cargo testWORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
使用的模块
配置CMakeList.txt
set(RUSTLIB_BUILD_DIR ${CMAKE_BINARY_DIR}/rustlib)
include_directories(${RUSTLIB_BUILD_DIR })
...
get_target_property(RUSTLIB_DIR rustlib LOCATION)
target_link_libraries(gui ${RUSTLIB_DIR }/librustlib.so)
add_dependencies(xxx rustlib)