前言
在上一篇文章使用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
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);
結果如下:
好了,到此爲止,我們就成功的將rust編譯成動態鏈接庫給JS調用了,這種方式是我覺得比較好的一種方式,雖然引入函數的方式比較醜,但是我們不用擔心node版本的問題。
結語
雖然FFI是一種我認爲比較好的方式,但是它也不是完美無缺的,例如,在跨越FFI的過程中,我們會丟失rust的類型信息,從而引發安全性問題,當然這也不是沒有解決辦法,我們可以使用rust的Box來包裝我們的類型,這個可以單獨開一篇文章來講述,就不展開了(先挖個坑,哪天想起來再填)