Rust FFI支持库ffi-support的使用:实现安全高效的跨语言互操作与C ABI兼容

Rust FFI支持库ffi-support的使用:实现安全高效的跨语言互操作与C ABI兼容

ffi-support是一个支持库,用于简化实现Rust与FFI(外部函数接口)之间的互操作模式,特别是Mozilla应用程序服务库(mozilla/application-services)中使用的"Rust组件"FFI库。

主要功能

  1. 避免将panic传递到FFI边界(这是未定义行为)
  2. 将Rust错误(和panic)转换为FFI调用方能够处理的错误
  3. 在Rust str和字符串之间进行转换
  4. 在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);
        }
    }
}

示例说明

  1. 我们定义了一个FfiResult结构体,用于表示FFI操作的结果
  2. 实现了从anyhow::ErrorFfiResult的转换,用于错误处理
  3. 使用FfiStr安全地处理来自FFI的字符串输入
  4. 提供了释放内存的函数ffi_result_free
  5. 使用#[no_mangle]确保函数名在编译后保持不变
  6. 使用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编写的动态库供其他语言调用
  • 构建混合语言系统时处理类型转换和内存管理

主要特性

  1. 安全的类型转换和内存管理
  2. 自动错误处理转换
  3. 简化字符串传递
  4. 支持复杂数据结构传递
  5. 线程安全保证

基本使用方法

添加依赖

首先在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;
}

最佳实践

  1. 内存管理:始终为FFI函数提供对应的释放函数
  2. 错误处理:使用ExternError传递错误信息
  3. 线程安全:确保FFI边界上的类型是线程安全的
  4. 文档:为所有FFI函数添加详细的文档说明
  5. 测试:编写跨语言测试验证接口行为

注意事项

  1. 所有暴露给FFI的函数必须使用#[no_mangle]extern "C"
  2. 避免在FFI边界传递复杂Rust类型
  3. 注意生命周期管理,避免悬垂指针
  4. 考虑使用cbindgen自动生成C头文件

ffi-support库大大简化了Rust与其他语言交互的复杂性,同时保持了Rust的安全性和性能优势。通过合理使用,可以构建出既安全又高效的跨语言系统。

回到顶部