当前位置: 首页 > news >正文

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)
http://www.lryc.cn/news/14086.html

相关文章:

  • 从全局变量寻找到Tomcat回显方式
  • Tapdata Connector 实用指南:数据入仓场景之数据实时同步到 BigQuery
  • 关于机器人状态估计(12)-VIO/VSLAM的稀疏与稠密
  • Python每日一练(20230220)
  • 技术总监的“技术提升”
  • kettle安装部署_简单认识_Spoon勺子界面---大数据之kettle工作笔记002
  • 第三章 Kafka生产问题总结及性能优化实践
  • Comparable和Comparator的区别
  • 全15万字丨PyTorch 深度学习实践、基础知识体系全集;忘记时,请时常回顾。
  • 简洁易用的记账小程序——微点记账
  • Windows平台上达梦数据库的ODBC安装与配置
  • 哈希表的介绍
  • spring cloud gateway 实现redis动态路由及自动项目路由上报
  • c++函数对象(仿函数)、谓词、内建函数对象
  • 物联网对供应链管理的影响
  • c++ 那些事 笔记
  • 心跳机制Redis
  • 蓝桥杯算法训练合集十七 1.数字反转2.试题39713.矮人采金子4.筛法5.机器指令
  • 第一章 初识 Spring Security
  • 2023-02-20 关于回朔的思考
  • 推荐系统[八]算法实践总结V1:淘宝逛逛and阿里飞猪个性化推荐:召回算法实践总结【冷启动召回、复购召回、用户行为召回等算法实战】
  • 适合初学者的超详细实用调试技巧(下)
  • C# String与StringBuilder 的区分
  • 【麒麟】基于GPS北斗卫星技术的NTP网络时间服务器
  • “互联网+”下劳动关系认定的现状
  • LPWAN及高效弹性工业物联网核心技术方案
  • OPTIONS FMTSEARCH
  • Python3 pip
  • 【2023-02-20】JS逆向之翼支付
  • 假如面试官问你Babel的原理该怎么回答