< 返回版块

洋芋 发表于 2020-04-16 17:16

Tags:rust, ffi

Rust 语言对 FFI 有比较完善的支持。本节主要讲在基础设施层面,Rust 语言对 FFI 的支持。

Rust 语言主要在关键字和标准库两个方面对 FFI 提供了支持,具体如下:

  • 关键字 extern
    • 属性 #[no_mangle]
    • 外部块 ExternBlock 及其属性 linklink_name
  • 标准库
    • std:os:raw 模块
    • std:ffi 模块

1. 关键字 extern

在 Rust 语言中,使用关键字extern可以实现 Rust 语言与其它语言的交互,这是 Rust 外部函数接口 FFI 的基础。

1.1 extern 函数

直接在 Rust 的函数关键字fn前使用关键字extern,可以创建一个允许其他语言调用 Rust 函数的接口。

同时可以通过使用 ABI 字符串[1]来指定具体的 ABI,其中有三个 ABI 字符串是跨平台的:

  • extern "Rust",默认的 ABI,在 Rust 代码中对任何普通函数fn foo()声明时都将使用该 ABI。
  • extern "C",指定使用 C-ABI,类似extern fn foo(),无论 C 编译器支持哪种默认设置。
  • extern "system",通常类似extern "C",但在 Win32 平台上,它是"stdcall",或用于链接到 Windows API。
// ffi/c-call-rust/src/lib.rs

pub extern "C" fn call_from_rust() {
    println!("This is a Rust function for C!");
}

1.2 属性 #[no_mangle]

属性no_mangle,用来关闭 Rust 的名称修改(name mangling)功能。Mangling 是编译器在解析名称时,修改我们定义的函数名称,增加一些用于其编译过程的额外信息。

但在与其它语言交互时,如果函数名称被编译器修改,程序开发者无法知道修改后的函数名称,其它语言也无法按原名称调用。执行1.1中示例代码时,报错信息如下:

Undefined symbols for architecture x86_64:
  "_call_from_rust", referenced from:
      _main in main-77cb59.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1

所以为了使 Rust 函数能在其它语言中被调用,必须禁用 Rust 编译器的名称修改功能。通过在1.1的示例代码中增加属性 #[no_mangle] ,告诉 Rust 编译器不要修改此函数的名称。

// ffi/c-call-rust/src/lib.rs
#[no_mangle]
pub extern "C" fn call_from_rust() {
    println!("This is a Rust function for C!");
}

1.3 外部块 ExternBlock

在 Rust 语言中,使用关键字extern可以声明一个外部块(ExternBlock),通过外部块的形式,可以在 Rust 代码中调用外部代码。

在 Rust 语言参考文档中,使用关键字extern声明一个外部块的语法格式如下:

extern Abi? {
    InnerAttribute*
    ExternalItem*
}

其中的Abi表示调用库使用的 ABI 标准,可选值为1.1节中提到的 ABI 字符串。缺省情况下,外部块默认为标准的 C-ABI。在定义外部块的时候,可以使用 linklink_name 这两个属性,通过它们来控制外部块的行为。

外部块的属性 link

属性link用来指定原生库的名称,编译器根据它为外部块链接原生库。它支持的键有:namekindwasm_import_modulename用来定义要链接的原生库的名称。 kind是一个可选值,通过它来指定原生库的类型,它有以下三种可选的值:

  • dylib,表示为动态库。如果未指定kind,则它为默认值。
  • static,表示为静态库。
  • framework,表示 macOS 的框架,这仅对 macOS 目标有效。

如果对属性link设定了原生库的类型kind,则必须包括原生库的名称name

wasm_import_module可用于指定 WebAssembly 模块的名称,如果未指定wasm_import_module,则模块名称默认为env

#[link(name = "c_library")]
extern "C" {
    fn c_function(input: i32) -> i32;
}
外部块的属性 link_name

在外部块内,通过属性link_name,指定原生库中函数或静态对象的名称,编译器根据它可以为外部块链接原生库并导入该名称定义的函数或静态对象。

extern "C" {
    #[link_name = "c_function_name"]
    fn name_in_rust();
}

外部块中声明的函数在 Rust 代码中是不安全的,因为其他语言不会强制执行 Rust 语言中的语法规则,故无法检查这些代码,所以程序开发者务必要确保这部分代码的安全。

// ffi/rust-call-c/src/main.rs
// 标准库<stdlib.h>内置的abs函数
extern "C" {
    #[link_name = "abs"]
    fn abs_in_rust(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("abs(-1) is {}", abs_in_rust(-1));
    }
}

2. 标准库

在实际开发 Rust 语言与其它语言相互调用的程序时,会遇到需要相互传递参数的情况。Rust 标准库std::os::rawstd::ffi 这两个模块提供了这方面的支持。

2.1 std::os::raw 模块

使用 FFI 进行交互的代码通常会使用到 C 语言提供的基本类型,标准库 std::os::raw 模块[2]提供了一些类型与 C 语言定义的类型相匹配,以便与 C 语言交互的代码引用正确的类型。

类型 解释
c_char 等同于 C 语言的char类型
c_double 等同于 C 语言的double类型
... ...

更多类型可以查见参考链接[2]。

2.2 标准库 std::ffi 模块

由于 Rust 语言中字符串与 C 语言字符串的不同之处,标准库 std::ffi 模块[3]提供了一组实用的程序,主要用于外部函数接口 FFI 的绑定,以及用在与其他语言传递类 C 字符串的代码中。

在支持 C-ABI 的语言(如:Python)中传递 UTF-8 字符串[4]时,CStringCStr很有用。

CStr

在 C 语言中生成的字符串,Rust 使用CStr来表示,它和str类型对应,表明并不拥有这个字符串的所有权。所以CStr表示一个以终止符\0结尾的字节数组的引用,如果它是有效的 UTF-8 字符串,则可以将其转换为 Rust 语言中的&str。实现从 C 语言到 Rust 语言的字符串传递。

CString

在 Rust 语言中生成的字符串,Rust 使用CString来表示用以传给 C 程序的字符串。CString以终止符\0结尾,并且没有内部\0字符,代码可以首先从 Rust 语言的普通字符串创建CString类型,然后将其作为参数传递给使用 C-ABI 约定的字符串函数。实现从 Rust 语言到 C 语言的字符串传递。

use std::ffi::CString;
use std::os::raw::c_char;

#[no_mangle]
pub extern rust_printer(input: *const c_char) {
    let mut hello = String::from("Hello World!");
    let c_str_to_print = CString::new(hello).unwrap();
}

注意:因为所有权概念是 Rust 语言特有的,所以在和 C 语言交互时,必须实现一个释放内存的方法供 C 代码调用。

此外在不同操作系统平台传输字符串,或者在捕获外部命令的输出时,OsStringOsStr很有用。

  • OsString表示传递给操作系统的拥有所有权的字符串。例如,env::var_os()用于查询环境变量,它返回一个Option<OsString>。如果环境变量存在,将获得Some(os_string),然后可以将其转换为 Rust 字符串。

  • OsStr表示传递给操作系统的字符串引用,可以按照与OsString类似的方式将其转换为 UTF-8 编码的 Rust 字符串切片。

另外,当用作指针时,std::ffi::c_void等同于 C 语言中的void类型。

示例代码:https://github.com/lesterli/rust-practice/tree/master/ffi

参考链接

[1] 外部块支持的 ABI 字符串,https://doc.rust-lang.org/reference/items/external-blocks.html

[2] 标准库 std::os::raw 模块,https://doc.rust-lang.org/stable/std/os/raw/index.html

[3] 标准库 std::ffi 模块,https://doc.rust-lang.org/std/ffi/index.html

[4] Rust 中 String 与 UTF-8 编码,https://mp.weixin.qq.com/s/ZX_0G6JcNMusLz6JJOkNSg

评论区

写评论

还没有评论

1 共 0 条评论, 1 页