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 入門資料

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/JuRr_CRoF6HlIQ-57VSP6g