天天看點

Rust FFI 程式設計 - Rust 語言層面對 FFI 的支援

Rust 語言對 FFI 有比較完善的支援。本節主要講在基礎設施層面,Rust 語言對 FFI 的支援。

Rust 語言主要在關鍵字和标準庫兩個方面對 FFI 提供了支援,具體如下:

  • 關鍵字

    extern

    • 屬性

      #[no_mangle]

    • 外部塊

      ExternBlock

      及其屬性

      link

      link_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。在定義外部塊的時候,可以使用

link

link_name

這兩個屬性,通過它們來控制外部塊的行為。

外部塊的屬性

link

屬性

link

用來指定原生庫的名稱,編譯器根據它為外部塊連結原生庫。它支援的鍵有:

name

kind

wasm_import_module

name

用來定義要連結的原生庫的名稱。

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::raw

std::ffi

這兩個子產品提供了這方面的支援。

2.1

std::os::raw

子產品

使用 FFI 進行互動的代碼通常會使用到 C 語言提供的基本類型,标準庫

std::os::raw

子產品[2]提供了一些類型與 C 語言定義的類型相比對,以便與 C 語言互動的代碼引用正确的類型。

類型

解釋

複制

更多類型可以查見參考連結[2]。

2.2 标準庫

std::ffi

子產品

由于 Rust 語言中字元串與 C 語言字元串的不同之處,标準庫

std::ffi

子產品[3]提供了一組實用的程式,主要用于外部函數接口 FFI 的綁定,以及用在與其他語言傳遞類 C 字元串的代碼中。

在支援 C-ABI 的語言(如:Python)中傳遞 UTF-8 字元串[4]時,

CString

CStr

很有用。

CStr

在 C 語言中生成的字元串,Rust 使用

CStr

來表示,它和

str

類型對應,表明并不擁有這個字元串的所有權。是以

CStr

表示一個以終止符

\n

結尾的位元組數組的引用,如果它是有效的 UTF-8 字元串,則可以将其轉換為 Rust 語言中的

&str

。實作從 C 語言到 Rust 語言的字元串傳遞。

CString

在 Rust 語言中生成的字元串,Rust 使用

CString

來表示用以傳給 C 程式的字元串。

CString

以終止符

\n

結尾,并且沒有内部

\n

字元,代碼可以首先從 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 代碼調用。

此外在不同作業系統平台傳輸字元串,或者在捕獲外部指令的輸出時,

OsString

OsStr

很有用。

  • 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