https://cloud.tencent.com/developer/article/2077534
https://github.com/shepmaster/rust-ffi-omnibus
$ 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")
}
}
https://github.com/markrenChina/rust_ffi
Cargo.toml 可能需要
[dependencies]
libc = "0.2.139"
[lib]
crate_type = ["cdylib"]
name = "demo"
cargo build --release
make
./test_c
all:demo
demo:main.c
#或者在这加入cargo build --release
gcc -o demo main.c -I/usr/include -L./target/release -ldemo
clean:
rm demo
chmod +x test_c
export LD_LIBRARY_PATH=$PWD/target/release:$LD_LIBRARY_PATH
./demo
#[no_mangle]
pub extern "C" fn addition(a: u32, b: u32) -> u32 {
a + b
}
#include
#include
#include
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内的内存。
Box::new将对象分配到堆上,
Box::into_raw(),获取原始指针,并且封印了这块堆上内存的析构,只有在Box::from_raw()之后,堆上这块内存的析构函数会重新开启,同时获得所有权。
ffi交互时,不同语言的栈内存结构(读写内存协议)存在差距,存在频繁使用堆内存的性能问题。
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++ 最好使用包装类,在析构中free对象
#include
#include
#include
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);
}
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()
}
#include
#include
#include
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);
}
入参 *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);
}
#include
#include
#include
extern uint32_t
print_c_string(const char *str);
int main(void) {
print_c_string("göes to élevên");
}
在to_owned时复制了数据,不怕C侧free掉原来的内存
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侧要主动销毁,推荐采用RAII方式。
这种方式是最普遍的,比如你调用某个rust函数,这个函数返回就是一个字符串,除非在包装上采用后面windows式的,不然使用这种方式返回字符串是最常见的。
示例中返回mut,推荐使用const,使用*const在free时,CString::from_raw()需要 as *mut _
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));
};
}
#include
#include
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);
}
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 _);
}
#include
#include
#include
#include
#include
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
}
具体见示例工程
#[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
}
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的字符串,但是可能需要注意线程。
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())
}
#include
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;
}
windows系统调用常见的方式。
不同上面的C侧主动释放的方式,传入缓冲区的方式是,C侧申请(malloc)C侧释放(free)的方式是常规的代码操作。
c++可以通过包装,传出std::string,析构释放。
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
}
#include
#include
#include
#include
#include
/**仿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语言方式的结构体内存布局方式,能被大部分高级语言解析并使用。
示例中#[repr©]建立一个名为Tuple 的结构体,命名上有些误解。在Rust侧,元组的写法为(a,b)。2个From实现,分别为rust的元组转Tuple结构体,Tuple结构体转rust元组。
如果有需求要传递一个#[repr©]对象数组,在支持initializer_list版本的C++中,可以在C++层包装一个initializer_list版本的函数,采用{{},{}}的方式入参。交互起来非常方便。
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()
}
#include
#include
#include
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);
}
拿到c侧的二级指针,rust侧得到vec后将二级指针的一级指针内存改为vec的地址。再主动让内存遗忘这块区域。
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栈上分配一个指针,传给rust的是2级指针(副作用编程,回传后一级指针的内容修改为数组首地址)。
#include
#include
#include
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);
}
简单介绍一下,使用时,在根目录下新建build文件夹,编译输出在这个文件夹下
cd build && cmake.. && make
假设我们的rust 模块叫 rustlib
add_subdirectory(rustlib)
# 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 ALL
COMMENT "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_test
COMMAND cargo test
WORKING_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)