Rust 學習資料
最近在研究 Rust,目前大多數項目都可以使用 Rust 開發,但是涉及到和其他語言交互,比如用 Rust 開發一個 SDK,一般還是需要導出 C 接口。
那如何將 Rust 導出 C 接口?
Rust 的 FFI 就是專門做這件事的。一個正常的 Rust public 接口長這樣:
pub fn hello_world() -> i32 {
20
}
如果要把一個 Rust 函數導出爲 C 接口,需要對它進行改造:
#[no_mangle]
pub extern "C" fn hello_world() -> i32 {
20
}
它相比於純 Rust 函數有兩點不同:
一個是 extern "C":表示導出 C 接口
一個是 #[no_mangle]:正常一個 C++ 或者 Rust 函數相關的符號都特別長且難以理解,使用它表示導出的符號是 C 符號,即沒有任何多餘的修飾,函數名是啥樣,相關的符號大概就是啥樣,如圖:
如何導出 C 的動態庫或者靜態庫?
如果想要編譯出動態庫或者靜態庫,可以在 Cargo.toml 中配置 crate-type:
[lib]
crate-type = ["cdylib"] # Creates dynamic lib
# crate-type = ["staticlib"] # Creates static lib
cylib 表示導出動態庫,staticlib 表示導出靜態庫。
如何生成對應的 C 頭文件?
一個 C 庫一般都有個頭文件,那 Rust 怎麼生成一個 C 的頭文件呢?可以使用 cbindgen:
cbindgen --config cbindgen.toml --crate hello_world --output hello_world.h
其中 cbindgen.toml 是一個 template 文件,我後面鏈接中列了具體地址。
上面的 hello_world 函數,我使用 cbindgen 就可以自動生成一個頭文件:
#pragma once
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
int32_t hello_world(void);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
至於如何使用更復雜的類型和 C 交互,比如我想導出和傳入一個結構體的指針,比如我想設置 const char*,以及怎麼管理對應的內存?
可以直接看這段代碼:
use std::boxed::Box;
use std::ffi::CStr;
use std::ffi::CString;
use std::os::raw::c_char;
pub struct Manager {
path: CString,
}
fn get_default_cstring() -> CString {
CString::new("").expect("new string failed")
}
#[no_mangle]
pub extern "C" fn manager_create() -> *mut Manager {
println!("{}", "create_manager().".to_string());
Box::into_raw(Box::new(Manager {
path: get_default_cstring(),
}))
}
#[no_mangle]
pub extern "C" fn manager_destroy(ptr: *mut Manager) {
if ptr.is_null() {
return;
}
// safe
unsafe {
let _b = Box::from_raw(ptr);
}
}
impl Manager {
#[no_mangle]
pub extern "C" fn manager_set_path(&mut self, p: *const c_char) {
unsafe {
self.path = CString::from(CStr::from_ptr(p));
}
}
#[no_mangle]
pub extern "C" fn manager_get_function(&self) -> *const c_char {
self.path.as_ptr()
}
}
#[no_mangle]
pub extern "C" fn hello_world() -> i32 {
20
}
也許有人不太理解 Rust 代碼的含義,那可以直接看它對應的 C Header:
#pragma once
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
typedef struct Manager Manager;
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
int32_t hello_world(void);
struct Manager *manager_create(void);
void manager_destroy(struct Manager *ptr);
const char *manager_get_function(const struct Manager *self);
void manager_set_path(struct Manager *self, const char *p);
#ifdef __cplusplus
} // extern "C"
#endif // __cplusplus
通過 manager_create 創建的內存,需要通過 manager_destroy 銷燬。
但是在對應的 Rust 代碼沒有用到申請或者銷燬內存相關的代碼,而是使用 Box 來管理內存,它可以理解爲 C++ 中的 unique_ptr。
內存都通過對象來管理,避免 Rust 申請的內存,讓 C 這邊來釋放,違反代碼的開發準則。
當然,如果你非要在 Rust 層想 malloc 和 free 內存,可以使用 libc 的 crate。
如果想在 Rust 中調用 C 或者 C++ 代碼,可以使用 cxx crate,也很方便,比如 Rust 中的 String、Vec 都在 cxx 中有對應的類型。
但是我的目的是使用 Rust 開發一個 C 的動態庫的 SDK,兩種方法都嘗試了下,感覺還是直接使用 FFI 更方便些。
我這裏特意整理了一些 Rust FFI 相關資料,感興趣的可以看看
-
Rust 如何調用 C 接口、Rust 如何導出 C 接口、C 的 callback 如何傳給 Rust:https://doc.rust-lang.org/nomicon/ffi.html
-
The Embedded Rust Book FFI:https://docs.rust-embedded.org/book/interoperability/rust-with-c.html
-
使用 cbindgen 可以自動分析,將 Rust 接口導出爲 C 接口:https://github.com/eqrion/cbindgen
-
cbindgen 的 default template:https://github.com/eqrion/cbindgen/blob/master/template.toml
-
cbindgen 的 doc:https://github.com/eqrion/cbindgen/blob/master/docs.md
-
Rust FFI 的 example blog,主要是 string 如何傳出去並銷燬相關內存:https://snacky.blog/en/string-ffi-rust.html
-
cxx 專用於 Rust 和 C++ 之間的橋樑:https://github.com/dtolnay/cxx
-
cxx:https://cxx.rs/
-
Complex data types and Rust FFI blog:http://kmdouglass.github.io/posts/complex-data-types-and-the-rust-ffi/
-
Rust std ffi:https://doc.rust-lang.org/std/ffi/index.html
-
與 C 交互時,可以使用 libc 在 Rust 層做 C 的 malloc 和 free
-
涉及 ptr 的地方需要了解:https://doc.rust-lang.org/std/ptr/index.html
還有些 Rust 入門資料:
-
https://www.zhihu.com/question/31038569
-
https://doc.rust-lang.org/rust-by-example/
-
https://doc.rust-lang.org/cargo/getting-started/installation.html
-
https://github.com/rustlang-cn/Rustt
-
https://github.com/sunface/rust-course
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/JuRr_CRoF6HlIQ-57VSP6g