Rust FFI支持库ffi-support的使用:实现安全高效的跨语言互操作与C ABI兼容
Rust FFI支持库ffi-support的使用:实现安全高效的跨语言互操作与C ABI兼容
ffi-support是一个支持库,用于简化实现Rust与FFI(外部函数接口)之间的互操作模式,特别是Mozilla应用程序服务库(mozilla/application-services)中使用的"Rust组件"FFI库。
主要功能
- 避免将panic传递到FFI边界(这是未定义行为)
- 将Rust错误(和panic)转换为FFI调用方能够处理的错误
- 在Rust str和字符串之间进行转换
- 在Rust和FFI调用方之间传递非字符串数据(包括暴露不透明指针、将数据序列化为JSON字符串,以及自定义处理方式)
使用方式
在Cargo.toml中添加以下依赖:
ffi-support = "0.4.4"
完整示例代码
以下是一个使用ffi-support实现跨语言互操作的完整示例:
use ffi_support::{FfiStr, IntoFfi};
// 定义一个FFI兼容的结构体
#[repr(C)]
pub struct FfiResult {
success: bool,
error_msg: *mut std::os::raw::c_char,
}
// 实现错误处理
impl From<anyhow::Error> for FfiResult {
fn from(err: anyhow::Error) -> Self {
let error_msg = err.to_string();
FfiResult {
success: false,
error_msg: ffi_support::rust_string_to_c(error_msg),
}
}
}
// 定义一个FFI函数
#[no_mangle]
pub extern "C" fn rust_ffi_example(input: FfiStr<'_>) -> FfiResult {
// 使用FfiStr安全地处理来自FFI的字符串
let input_str = match input.as_opt_str() {
Some(s) => s,
None => return FfiResult {
success: false,
error_msg: ffi_support::rust_string_to_c("Invalid input string".to_string()),
},
};
// 处理逻辑
match process_input(input_str) {
Ok(_) => FfiResult {
success: true,
error_msg: std::ptr::null_mut(),
},
Err(e) => e.into(),
}
}
fn process_input(input: &str) -> anyhow::Result<()> {
// 示例处理逻辑
if input.is_empty() {
anyhow::bail!("Input cannot be empty");
}
println!("Processed input: {}", input);
Ok(())
}
// 定义释放内存的函数
#[no_mangle]
pub extern "C" fn ffi_result_free(result: FfiResult) {
if !result.error_msg.is_null() {
unsafe {
ffi_support::destroy_c_string(result.error_msg);
}
}
}
示例说明
- 我们定义了一个
FfiResult
结构体,用于表示FFI操作的结果 - 实现了从
anyhow::Error
到FfiResult
的转换,用于错误处理 - 使用
FfiStr
安全地处理来自FFI的字符串输入 - 提供了释放内存的函数
ffi_result_free
- 使用
#[no_mangle]
确保函数名在编译后保持不变 - 使用
extern "C"
指定C ABI调用约定
许可证
双重许可: Apache License 2.0 或 MIT license
1 回复
Rust FFI支持库ffi-support的使用:实现安全高效的跨语言互操作与C ABI兼容
介绍
ffi-support
是一个Rust库,旨在简化Rust与其他语言通过FFI(外部函数接口)交互的过程。它提供了安全、高效的工具来处理跨语言边界的数据转换和错误处理,同时确保与C ABI兼容。
这个库特别适合以下场景:
- 将Rust代码嵌入到其他语言应用中(C/C++/Python/Ruby等)
- 创建Rust编写的动态库供其他语言调用
- 构建混合语言系统时处理类型转换和内存管理
主要特性
- 安全的类型转换和内存管理
- 自动错误处理转换
- 简化字符串传递
- 支持复杂数据结构传递
- 线程安全保证
基本使用方法
添加依赖
首先在Cargo.toml
中添加依赖:
[dependencies]
ffi-support = "0.4"
基本示例:暴露Rust函数给C
use ffi_support::FfiStr;
#[no_mangle]
pub extern "C" fn rust_greet(name: FfiStr<'_>) -> *mut std::os::raw::c_char {
ffi_support::rust_string_to_c(format!("Hello, {}!", name.as_str()))
}
#[no_mangle]
pub extern "C" fn free_string(s: *mut std::os::raw::c_char) {
unsafe { ffi_support::destroy_c_string(s) }
}
在C中调用
#include <stdio.h>
// 声明Rust函数
extern char* rust_greet(const char* name);
extern void free_string(char* s);
int main() {
char* greeting = rust_greet("World");
printf("%s\n", greeting);
free_string(greeting);
return 0;
}
高级用法
处理复杂数据结构
use ffi_support::{ExternError, ErrorCode};
use std::convert::TryFrom;
#[repr(C)]
pub struct Point {
x: f64,
y: f64,
}
#[no_mangle]
pub extern "C" fn distance(p1: Point, p2: Point) -> f64 {
((p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sqrt()
}
#[no_mangle]
pub extern "C" fn midpoint(p1: Point, p2: Point, err: *mut ExternError) -> Point {
ffi_support::abort_on_ppanic::call_with_result(err, || {
Ok(Point {
x: (p1.x + p2.x) / 2.0,
y: (p1.y + p2.y) / 2.0,
})
})
}
错误处理
#[no_mangle]
pub extern "C" fn parse_number(
s: FfiStr<'_>,
err: *mut ExternError,
) -> i32 {
ffi_support::abort_on_panic::call_with_result(err, || {
s.as_str()
.parse()
.map_err(|_| ExternError::new_error(ErrorCode::new(1), "Failed to parse number"))
})
}
完整示例代码
Rust部分 (lib.rs)
use ffi_support::{FfiStr, ExternError, ErrorCode};
// 基本字符串处理示例
#[no_mangle]
pub extern "C" fn rust_greet(name: FfiStr<'_>) -> *mut std::os::raw::c_char {
// 将Rust字符串转换为C字符串
ffi_support::rust_string_to_c(format!("Hello, {}!", name.as_str()))
}
#[no_mangle]
pub extern "C" fn free_string(s: *mut std::os::raw::c_char) {
// 释放C字符串内存
unsafe { ffi_support::destroy_c_string(s) }
}
// 复杂数据结构示例
#[repr(C)]
pub struct Point {
x: f64,
y: f64,
}
#[no_mangle]
pub extern "C" fn distance(p1: Point, p2: Point) -> f64 {
// 计算两点间距离
((p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sqrt()
}
#[no_mangle]
pub extern "C" fn midpoint(p1: Point, p2: Point, err: *mut ExternError) -> Point {
// 计算中点,使用错误处理
ffi_support::abort_on_panic::call_with_result(err, || {
Ok(Point {
x: (p1.x + p2.x) / 2.0,
y: (p1.y + p2.y) / 2.0,
})
})
}
// 错误处理示例
#[no_mangle]
pub extern "C" fn parse_number(
s: FfiStr<'_>,
err: *mut ExternError,
) -> i32 {
// 字符串解析为数字,带错误处理
ffi_support::abort_on_panic::call_with_result(err, || {
s.as_str()
.parse()
.map_err(|_| ExternError::new_error(ErrorCode::new(1), "Failed to parse number"))
})
}
C调用部分 (main.c)
#include <stdio.h>
#include <stdint.h>
// 声明Rust函数
extern char* rust_greet(const char* name);
extern void free_string(char* s);
// 定义Point结构体与Rust中一致
typedef struct {
double x;
double y;
} Point;
extern double distance(Point p1, Point p2);
extern Point midpoint(Point p1, Point p2, uint32_t* err);
extern int32_t parse_number(const char* s, uint32_t* err);
int main() {
// 测试字符串处理
char* greeting = rust_greet("World");
printf("String demo: %s\n", greeting);
free_string(greeting);
// 测试几何计算
Point p1 = {1.0, 2.0};
Point p2 = {4.0, 6.0};
printf("Distance: %f\n", distance(p1, p2));
uint32_t err = 0;
Point mid = midpoint(p1, p2, &err);
printf("Midpoint: (%f, %f)\n", mid.x, mid.y);
// 测试错误处理
int32_t num = parse_number("42", &err);
printf("Parsed number: %d\n", num);
num = parse_number("invalid", &err);
printf("Error parsing: %d\n", err);
return 0;
}
最佳实践
- 内存管理:始终为FFI函数提供对应的释放函数
- 错误处理:使用
ExternError
传递错误信息 - 线程安全:确保FFI边界上的类型是线程安全的
- 文档:为所有FFI函数添加详细的文档说明
- 测试:编写跨语言测试验证接口行为
注意事项
- 所有暴露给FFI的函数必须使用
#[no_mangle]
和extern "C"
- 避免在FFI边界传递复杂Rust类型
- 注意生命周期管理,避免悬垂指针
- 考虑使用
cbindgen
自动生成C头文件
ffi-support
库大大简化了Rust与其他语言交互的复杂性,同时保持了Rust的安全性和性能优势。通过合理使用,可以构建出既安全又高效的跨语言系统。