本文主要是介绍外部函数接口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
的偏移量是无法确定的。所以这种类型不适合使用C
的FFI
。
作者尝试动态加载实现插件,发现RUST ABI
不稳定带来的问题比想象的更加严重。在这之前,他一直认为即使RUST ABI
不稳定,只要库和主二进制文件是用相同的编译器以及std
等版本编译的,就可以安全地动态加载一个库。然而事实证明,ABI
不仅仅是可能在不同编译版本之间发生”断裂“,在编译器执行的过程中也会发生断裂,即RUST
编译器并不保证同一个类型的布局在每次执行的时候都一致,类型布局可以随着每次编译而改变。所以他的方案是使用#[repr(C)]
的C-ABI
以及使用abi_stable
来获得稳定的std
库。
作者后面尝试使用abi_stable
来开发插件系统。abi_stable
是按模块来构建的,并且提供了很多FFI
安全的类型(指FFI
边界提供了稳定的内存布局),包括trait
对象的支持及提供了处理FFI
边界panic
的方法。
这篇关于外部函数接口FFI的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!