Rust 與 C 進行互操作
可將 Rust 集成到現有的 C 代碼庫中,也可利用現有的 SDK 編寫 Rust 應用程序。
Rust 和 C 之間的互操作依賴於兩種語言之間的數據轉換,爲此,有一個專門的模塊 ffi。
std::ffi 提供 C 基元類型的類型定義,如 char、int、long。它還提供了一些用於轉換更復雜的實用程序類型,例如 String,將兩者進行映射更容易也更安全的進行處理。
C 基元類型的值可以用作相應的 Rust 類型之一,反之亦然,因爲前者只是後者的類型別名。例如,以下代碼在 32 位的平臺上編譯。
fn foo(num: u32) {
let c_num: c_uint = num;
let r_num: u32 = c_num;
}
在 Rust 應用中調用 C
在 Rust 項目中使用 C 或 C++ 由兩個主要部分組成,1,包裝要用於 Rust 的待導出的 C API;2,構建要與 Rust 代碼集成的 C 或 C++ 代碼。由於 C++ 沒有穩定的 ABI 供 Rust 編譯器作爲目標,因此建議在將 Rust 與 C 或 C++ 結合時推薦使用 C ABI。
定義接口
在 Rust 使用 C 或 C++ 代碼前,需要數據類型和函數簽名在鏈接代碼中存在。在 C 或 C++ 中,你需要包含定義數據的頭文件。在 Rust 中,你需要手動或使用工具將這些頭文件定義的轉換爲 Rust 的相適應的定義。
首先,我們介紹如何手動將這些定義從 C 或 C++ 轉換爲 Rust。
定義的頭文件如下:
/* File: cool.h */
typedef struct CoolStruct {
int x;
int y;
} CoolStruct;
void cool_function(int i, char c, CoolStruct* cs);
翻譯成 Rust 時,看起來像這樣,如下:
/* File: cool_bindings.rs */
#[repr(C)]
pub struct CoolStruct {
pub x: cty::c_int,
pub y: cty::c_int,
}
extern "C" {
pub fn cool_function(
i: cty::c_int,
c: cty::c_char,
cs: *mut CoolStruct
);
}
#[repr(C)] 屬性,指示 Rust 編譯器始終使用與 C 相同的規則來組織結構中的數據。extern "C" , 此語句定義使用 C ABI 的函數的簽名。此函數定義了簽名而不定義函數的實現,此函數的實現需要在其他地方提供,或者從靜態庫鏈接到 Rust 庫或二進制文件。cool_function 的參數 cs 是一個裸指針,其它參數使用與 C 兼容的類型。
與其手動生成這些接口(這很乏味且容易出錯),不如使用一個 bindgen 的工具自動執行這些轉換。工具的執行過程包括以下內容:
1,收集所有 C 或 C++ 的頭文件,轉換定義爲 Rust 的接口或數據類型。
2,編寫一個文件,該文件時在第一步中收集的每個文件。
3,提供此文件及用於編譯的編譯標誌。
4,bindgen 將生成的 Rust 代碼寫入到目標文件中。
由於 Rust 編譯器不知道如何編譯 C 或 C++ 的代碼,你需要自行編譯,對於嵌入式項目,這通常將 C 或 C++ 代碼編譯爲靜態庫,然後再鏈接步驟中與 Rust 代碼組合。如果你要使用的庫已經作爲靜態庫分發,則無需重新生成代碼,只需如上所述轉換提供的接口文件,並在編譯鏈接時包含靜態庫。如果你的代碼以源代碼存在,則需要通過構建系統或 cc 庫將 C 或 C++ 代碼編譯到靜態庫中。
在 C 應用中使用 Rust
在 C 或 C++ 使用 Rust 代碼也是由兩個部分組成:1,創建 C API;2,在 C 或 C++ 應用中使用你的 Rust 項目。
設置你的 Rust 項目,在 cargo.toml 文件中設置:
[lib]
name = "your_crate"
crate-type = ["cdylib"] # Creates dynamic lib
# crate-type = ["staticlib"] # Creates static lib
構建 C API
#[no_mangle] Rust 編譯器對符號名稱的修改方式與本機代碼鏈接器所期望的有所不同,這個屬性告訴 Rust 編譯器不要破壞導出的函數。
extern "C" 默認情況下,你在 Rust 中編寫的任何函數都將使用 Rust ABI(也不穩定)。如果構建面向外部的 FFI API 時,你需要告訴編譯器使用系統 ABI。
將這些結合在一起,你會得到一個大致如下所示的函數。
#[no_mangle]
pub extern "C" fn rust_function() {
}
就像你在 Rust 項目中使用代碼一樣,實現函數部分即可。
cargo 將依據你的平臺或設置生成一個 my_lib 的文件,後綴名可能爲. so,.dll,.a。這個庫可以鏈接到你的構建系統。
在 C 代碼中調用 Rust 函數,還需要一個頭文件來聲明函數簽名。Rust ffi API 的每個函數都需要一個對應的頭文件中的函數簽名。例如:
#[no_mangle]
pub extern "C" fn rust_function() {}
可以轉換爲
void rust_function();
有一個工具可以自動化這個過程。稱爲 cbindgen,它分析你的 Rust 代碼,然後從中生成 C 和 C++ 項目的頭文件。此時,使用 Rust 函數就是如此簡單:
#include "my-rust-project.h"
rust_function();
現如今,這兩種情況都有應用場景,例如,一些歷史存在的 C 項目,一些功能已經很穩定,這時使用 Rust,需要調用 C。同樣的,你寫好的 Rust 的功能,通過 FFI 生成的庫,可以應用到 Android 或 IOS 系統中。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/XlwCVINYa-MCk2epGuftEQ