使用Rust + Electron開發跨平臺桌面應用 ( 二 )

前言

在上一篇文章使用Rust + Electron開發跨平臺桌面應用 ( 一 )中,我們將Rust + Electron結合起來,使用Rust編寫核心業務邏輯,並編譯成node庫提供給Electron的UI界面調用,但是在上一篇文章中發現遇到了很多問題,尤其是Electron 的版本和 Rust編譯出來的版本必須要一致,否則會無法調用成功,這就很坑了,所以爲了改變這一情況,今天我們將使用另一種方式將Rust的代碼提供給Js進行調用,這就是FFI。

FFI是什麼

FFI(Foreign Function Interface)是用來與其它語言交互的接口,由於現實中很多程序是由不同編程語言寫的,必然會涉及到跨語言調用,這時一般有兩種解決方案:

1、將函數做成一個服務,通過進程間通信(IPC)或網絡協議通信(RPC, RESTful等);

2、直接通過 FFI 調用。

前者需要至少兩個獨立的進程才能實現,而後者直接將其它語言的接口內嵌到本語言中,所以調用效率比前者高。

Rust作爲系統級編程語言,也是對FFI提供了完善的支持。

mangle

由於rust支持重載,所以函數名會被編譯器進行混淆,就像c++一樣。因此當你的函數被編譯完畢後,函數名會帶上一串表明函數簽名的字符串。
這樣的函數名爲ffi調用帶來了困難,因此,rust提供了#[no_mangle]屬性爲函數修飾。 對於帶有#[no_mangle]屬性的函數,rust編譯器不會爲它進行函數名混淆, 如:

#[no_mangle]
pub extern fn test() {}

下面我們來編寫一個thread_count.rs,其實跟尋常的rust代碼沒有什麼區別:

#[no_mangle]
pub extern fn threadcount(x: i32) -> i32 {
    let result: i32 = num_cpus::get() as i32;
    return result * x;
}

指定庫類型

rust默認編譯成rust自用的rlib格式庫,要讓rust編譯成動態鏈接庫或者靜態鏈接庫,需要顯示指定,一共有三種方式,我這裏採用的是直接在Cargo.Toml文件中指定,如下:

[lib]
name = "thread_count"
crate-type = ["dylib"]

需要注意的是name,必須符合rust的包結構,能夠在src目錄下找到。

我們執行cargo build命令,可以看到,在/target/debug目錄下生成了我們需要的文件libthread_count.dylib

clipboard.png

JS使用rust的動態鏈接庫

那麼我們要如何在JS中調用rust生成dylib呢?答案就是ffi-napi,我們使用ffi-napi這個包來在js中調用ffi,話不多說,直接看代碼

let ffi = require('ffi-napi');
let path = require('path');

let threadCount = ffi.Library(path.join(__dirname, './target/debug/libthread_count'), {
    threadcount: ['int', ['int']]
});

let result = threadCount.threadcount(12);
console.log("thead_count: " + result);

結果如下:

clipboard.png

好了,到此爲止,我們就成功的將rust編譯成動態鏈接庫給JS調用了,這種方式是我覺得比較好的一種方式,雖然引入函數的方式比較醜,但是我們不用擔心node版本的問題。

結語

雖然FFI是一種我認爲比較好的方式,但是它也不是完美無缺的,例如,在跨越FFI的過程中,我們會丟失rust的類型信息,從而引發安全性問題,當然這也不是沒有解決辦法,我們可以使用rust的Box來包裝我們的類型,這個可以單獨開一篇文章來講述,就不展開了(先挖個坑,哪天想起來再填)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章