1.FFI (Foreign Function Interface) 是 Rust 中非常重要的一个部分,它主要在两个领域应用较广泛:
第一个是系统开发,这部分包括对 OS 系统调用的封装,对系统底层 C 库的封装,广泛平台的嵌入式/IoT 开发,OS 开发,充分利用现代硬件平台的性能优势。
第二个是跨语言开发,可以使用 Rust 语言为其它(几乎任何)语言写调用库,互相调用等。
利用 FFI,Rust 终能迅速切入巨大的存量遗产,同时也能牢牢把握新兴增量领域。螃蟹怎么横着走?就是有了FFI。
2.C语言和Rust的相互调用,最核心的就是指针的操作,两边的代码使用的是同一个程序栈,栈上的指针能放心地传递,而不用担心被错误释放的问题(栈上内存被调用规则自动管理,C和Rust中都是如此)。两边的代码可能使用不同的堆分配器,因此,堆上的指针的传递需要严格注意,需要各自管理各自的资源,谁创建谁释放,指针传递过程中,需要分析所有权问题。栈和堆的操作是不同的,这里一定要注意。
前面两个例子是最简单的整型类型的参数传递,能说明 Rust 导出共享库的基本模板操作,但在函数参数这块儿,能说明的问题有限。
C调用Rust,传入一个数组并返回一个求和之后的数。
C语言代码:
#include
#include
extern uint32_t sum_of_array(const uint32_t *numbers, size_t length);
int main(void) {
uint32_t numbers[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
unsigned int length = sizeof(numbers) / sizeof(*numbers);
uint32_t sum = sum_of_array(numbers, length);
printf("print in c, sum is: %d\n", sum);
}
Rust代码:
use std::slice;
#[no_mangle]
pub extern "C" fn sum_of_array(array: *const u32, len: usize) -> u32 {
let array = unsafe {
assert!(!array.is_null());
slice::from_raw_parts(array, len)
};
array.iter().sum()
}
对上面的代码解读:
1.对于数组的传递,分成了两个部分,一个是数组地址指针的传递(首元素指针),另一个是数组的长度(即元素的个数,非字节数)。
2.对于C语言中,数组是分配在栈上的,Rust 代码中,参数中的 *const u32 就对应 C 中的 const uint32_t *。
3.C语言传入的指针,Rust必须保证是绝对不能为空指针的,所以使用assert进行捕获。
4.slice::from_raw_parts(array,len)的作用是从一个指针和一个长度形成一个切片,长度还是元素的个数。这里的array就是切片类型了。
5.Rust 拿到 C 传递过来的指针后,标准的规范是:
尽早转换为 Rust 的安全类型进行操作。也就是说,保证不安全(unsafe块中的)的代码尽量少,并且直接使用这个指针的代码尽可能的少,转换成 Rust 中的标准类型再用。
尽量保证 zero cost。避免不必要的内存 copy 操作,影响性能。
为满足第一条规则,在转换前,我们的代码没有任何相关的代码。
为满足第二条规则,这里使用了 slice 类型,而不是 Vec 类型,slice切片属于部分引用,而vec属于堆分配部分的使用,会进行内存copy。