Rust 語言對 FFI 有比較完善的支援。本節主要講在基礎設施層面,Rust 語言對 FFI 的支援。
Rust 語言主要在關鍵字和标準庫兩個方面對 FFI 提供了支援,具體如下:
- 關鍵字
extern
- 屬性
#[no_mangle]
- 外部塊
及其屬性ExternBlock
和link
link_name
- 屬性
- 标準庫
-
子產品std:os:raw
-
子產品std:ffi
-
1. 關鍵字 extern
extern
在 Rust 語言中,使用關鍵字
extern
可以實作 Rust 語言與其它語言的互動,這是 Rust 外部函數接口 FFI 的基礎。
1.1 extern
函數
extern
直接在 Rust 的函數關鍵字
fn
前使用關鍵字
extern
,可以建立一個允許其他語言調用 Rust 函數的接口。
同時可以通過使用 ABI 字元串[1]來指定具體的 ABI,其中有三個 ABI 字元串是跨平台的:
-
,預設的 ABI,在 Rust 代碼中對任何普通函數extern "Rust"
聲明時都将使用該 ABI。fn foo()
-
,指定使用 C-ABI,類似extern "C"
,無論 C 編譯器支援哪種預設設定。extern fn foo()
-
,通常類似extern "system"
,但在 Win32 平台上,它是"stdcall",或用于連結到 Windows API。extern "C"
// 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]
屬性
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
ExternBlock
在 Rust 語言中,使用關鍵字
extern
可以聲明一個外部塊(ExternBlock),通過外部塊的形式,可以在 Rust 代碼中調用外部代碼。
在 Rust 語言參考文檔中,使用關鍵字
extern
聲明一個外部塊的文法格式如下:
extern Abi? {
InnerAttribute*
ExternalItem*
}
複制
其中的
Abi
表示調用庫使用的 ABI 标準,可選值為1.1節中提到的 ABI 字元串。預設情況下,外部塊預設為标準的 C-ABI。在定義外部塊的時候,可以使用
link
和
link_name
這兩個屬性,通過它們來控制外部塊的行為。
外部塊的屬性 link
link
屬性
link
用來指定原生庫的名稱,編譯器根據它為外部塊連結原生庫。它支援的鍵有:
name
,
kind
和
wasm_import_module
,
name
用來定義要連結的原生庫的名稱。
kind
是一個可選值,通過它來指定原生庫的類型,它有以下三種可選的值:
-
,表示為動态庫。如果未指定dylib
,則它為預設值。kind
-
,表示為靜态庫。static
-
,表示 macOS 的架構,這僅對 macOS 目标有效。framework
如果對屬性
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
在外部塊内,通過屬性
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
子產品
std::os::raw
使用 FFI 進行互動的代碼通常會使用到 C 語言提供的基本類型,标準庫
std::os::raw
子產品[2]提供了一些類型與 C 語言定義的類型相比對,以便與 C 語言互動的代碼引用正确的類型。
類型
解釋
複制
更多類型可以查見參考連結[2]。
2.2 标準庫 std::ffi
子產品
std::ffi
由于 Rust 語言中字元串與 C 語言字元串的不同之處,标準庫
std::ffi
子產品[3]提供了一組實用的程式,主要用于外部函數接口 FFI 的綁定,以及用在與其他語言傳遞類 C 字元串的代碼中。
在支援 C-ABI 的語言(如:Python)中傳遞 UTF-8 字元串[4]時,
CString
和
CStr
很有用。
CStr
CStr
在 C 語言中生成的字元串,Rust 使用
CStr
來表示,它和
str
類型對應,表明并不擁有這個字元串的所有權。是以
CStr
表示一個以終止符
\n
結尾的位元組數組的引用,如果它是有效的 UTF-8 字元串,則可以将其轉換為 Rust 語言中的
&str
。實作從 C 語言到 Rust 語言的字元串傳遞。
CString
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>
,然後可以将其轉換為 Rust 字元串。Some(os_string)
-
表示傳遞給作業系統的字元串引用,可以按照與OsStr
類似的方式将其轉換為 UTF-8 編碼的 Rust 字元串切片。OsString
另外,當用作指針時,
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