外部函数接口FFI

在某些场景下,你的RUST代码可能需要与另外一种语言编写的代码进行交互。RUST为此提供了extern关键字来简化创建和使用外部函数接口(Foreign Function Interface,FFI)。FFI是编程语言定义函数的一种方式,它允许其它编程语言来调用这些函数。

extern "C" {
   
    fn abs(input: i32) -> i32;
}

fn main() {
   
    unsafe {
   
        println!("Hello, world!{}", abs(-3));
    }
}

这段代码在extern "C"列出了我们想要调用的外部函数名称及签名,其中的"C"指明了外部函数使用的二进制接口(Application Binary Interface,ABI):它被用来定义函数在汇编层面的调用方式。我们使用的"C" ABI正是C编程语言的ABI,也是最常见的ABI格式之一。

ABI前世今生

我们浏览了Rust-ABI 的前世今生这篇博客。

在计算机软件中,应用二进制接口(ABI)是两个二进制程序模块之间的接口;通常这些模块之一是库或操作系统工具,而另一个是用户正在运行的程序。

C-ABI包含两个关键的内核:

  • 数据的内存布局方式
  • 函数如何调用

RUST目前的ABI并不稳定,即RUST不保证内存中数据结构的调用约定和内存布局不被改变。

这里有几个示例来说明什么是不稳定的ABI

// 虽然下面的结构体本质是相同的,但是 Rust 编译器不保证给予它们字段相同的内存偏移量
struct A(u32, u64);
struct B(u32, u64);

// Rust 编译器不保证字段的顺序和定义的一样
struct Rect {
   
    x: f32,
    y: f32,
    w: f32,
    h: f32,
}

RUST编译器会对上面的结构体进行优化,如果内存布局是确定的,就不利于优化了。比如没有办法对结构体字段进行重排以便达到最小化内存占用的优化目标。内存布局不确定性也有利于模糊测试(Fuzzer),因为模糊测试需要将字段随机排列以便更容易地暴露潜在的问题。

#[repr(C)]
struct MyStruct {
   
    x: u32,
    y: Vec<u8>,
    z: u32,
}

对于该示例来说,虽然使用了#[repr(C)]让结构体字段的顺序确定了,但是字段的偏移量依然无法确定,因为Vec<8>没有任何确定性的排序,从而z的偏移量是无法确定的。所以这种类型不适合使用CFFI

作者尝试动态加载实现插件,发现RUST ABI不稳定带来的问题比想象的更加严重。在这之前,他一直认为即使RUST ABI不稳定,只要库和主二进制文件是用相同的编译器以及std等版本编译的,就可以安全地动态加载一个库。然而事实证明,ABI不仅仅是可能在不同编译版本之间发生”断裂“,在编译器执行的过程中也会发生断裂,即RUST编译器并不保证同一个类型的布局在每次执行的时候都一致,类型布局可以随着每次编译而改变。所以他的方案是使用#[repr(C)]C-ABI以及使用abi_stable来获得稳定的std库。

作者后面尝试使用abi_stable来开发插件系统。abi_stable是按模块来构建的,并且提供了很多FFI安全的类型(指FFI边界提供了稳定的内存布局),包括trait对象的支持及提供了处理FFI边界panic的方法。

repr(C)定义兼容C的内存布局

要定义兼容C结构体的RUST结构体类型,需要使用repr(C)属性。在结构体声明上注释#[repr(C)],表示让RUST在内存中使用与C布局其结构体相同的方式来布局当前的结构体。

use std::ffi::{
   c_char, c_int};

#[repr(C)]
pub struct git_error {
   
    pub message: *const c_char,
    pub klass: c_int,
}

#[repr(C)]属性只影响结构体本身的布局。例子中的结构体要匹配C结构体,结构体的字段类型就必须使用C的类型:*const c_char对应char *等。

这个特例中,#[repr(C)]属性可能不会改变git_error的布局,指针和整数的布局方式并不多。但CC++保证结构体的成员以声明它们的顺序出现在内存中,且每个成员都有不同的地址,而RUST为了减少结构体占用的内存会对字段重新排序,没有大小的类型不占用空间。这里的#[repr(C)]属性告诉RUST对给定的类型遵守C的规则。

这么看来,RUSTC之间传递字符串就很困难。C将字符串表示为一个指向字符数组的指针,以一个空字符结束。而RUST可能保存为String,也可能是一个胖指针&str。这意味着不能把RUST字符串借用为C字符串。

abi_stable 库的使用用例

针对RUST-to-RUST的动态调用,关注点在程序启动时的动态加载以及类型检查。这个库允许定义RUST在运行时动态加载的库,及时这些加载的库和主项目有不同版本的版本依赖。

这个库的使用场景:

  • RUST依赖树从静态编译转换为一静态个链接库、或动态链接库,从而允许对更改进行单独的重新编译。
  • 创建一个插件系统(不支持卸载插件)

相关推荐

  1. 外部函数接口FFI

    2023-12-18 01:28:04       61 阅读
  2. numpy.fft 与 torch.fft函数使用

    2023-12-18 01:28:04       31 阅读
  3. ctypes --- Python 的外部函数

    2023-12-18 01:28:04       50 阅读
  4. Kotlin函数接口

    2023-12-18 01:28:04       57 阅读
  5. C++ 万能函数接口

    2023-12-18 01:28:04       46 阅读
  6. NC6外部交换平台新增单据接口开发说明

    2023-12-18 01:28:04       56 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2023-12-18 01:28:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2023-12-18 01:28:04       101 阅读
  3. 在Django里面运行非项目文件

    2023-12-18 01:28:04       82 阅读
  4. Python语言-面向对象

    2023-12-18 01:28:04       91 阅读

热门阅读

  1. LeetCode 15 三数之和

    2023-12-18 01:28:04       55 阅读
  2. 1131 - 删除指定字符

    2023-12-18 01:28:04       57 阅读
  3. DockerFile

    2023-12-18 01:28:04       56 阅读
  4. vi/vim的工作模式

    2023-12-18 01:28:04       59 阅读
  5. Android - 分区存储 MediaStore、SAF

    2023-12-18 01:28:04       58 阅读
  6. 【NTN 卫星通信】Starlink,费用、服务、市场(二)

    2023-12-18 01:28:04       169 阅读
  7. (第23天)Oracle 数据泵用户导出导入

    2023-12-18 01:28:04       58 阅读
  8. 自然语言处理(NLP)技术

    2023-12-18 01:28:04       55 阅读