1. 程式人生 > 其它 >Flutter + Rust ffi 開發跨平臺 UI 程式入門

Flutter + Rust ffi 開發跨平臺 UI 程式入門

Flutter + Rust ffi 開發跨平臺 UI 程式入門
楓安Maplean
已於 2022-04-28 21:01:55 修改 1491
收藏 1
文章標籤: flutter rust
版權
Flutter + Rust ffi 開發跨平臺 UI 程式入門

最近一直使用 Rust 開發程式,就研究了一下如何使用 rust 進行桌面程式的開發,發現有兩個比較流行的方法,其一是使用 Tauri,基於 WebVie;其二則是使用 Flutter,因為擔心 web 的效能問題,所以研究了一下 Flutter。在這裡記錄一下基本方法。
先做一個簡單的實現

Flutter 的示例小程式是一個計數器,通過點選按鈕來使螢幕上的數字自增。本文的目的就是改變這個小程式,使得自增這個過程在 rust 中完成。
RUST 後端程式

首先我們先來寫 RUST 後端,使用 cargo new 建立一個新的 RUST 程式。

cargo new rust-ffi-backend

1

編譯符合 C ABI 的連結庫

接下來我們來寫點程式碼,編輯 lib.rs 檔案,實現呼叫一次函式,全域性變數自增,為了和 Flutter 的示例程式做出區別,我們呼叫一次自增 2。

static mut COUNT: u32 = 0;

#[no_mangle]
pub unsafe extern "C" fn count_add_self() -> u32 {
COUNT += 2;
COUNT
}

現在新增需要編輯 Cargo.toml 檔案,將目標生成為符合 C ABI 的動態連結庫。

[lib]
name = "rust_ffi_backend"
crate-type = ["cdylib"]

1
2
3

我們先來編譯一下我們的 RUST 程式:

cargo build --release

1

生成 C 語言標頭檔案

接下來我們可以使用一個開源專案 cbindgen,他會幫助我們生成 C 語言的標頭檔案。

首先我們安裝它:

cargo install --force cbindgen

1

注意 cbingen 可能依賴於 LLVM 等許多其他程式,如果安裝失敗請詳細看提示,安裝缺少的元件。

然後在我們的專案目錄下建立一個 cbindgen.toml 檔案,比如它可能是下面這個樣子:

# This is a template cbindgen.toml file with all of the default values.
# Some values are commented out because their absence is the real default.
#
# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml
# for detailed documentation of every option here.

language = "C"

############## Options for Wrapping the Contents of the Header #################

header = "/* Text to put at the beginning of the generated file. Probably a license. */"
no_includes = true
after_includes = "typedef unsigned int uint32_t;"

############################ Code Style Options ################################

braces = "SameLine"
line_length = 100
tab_width = 4
documentation = true
documentation_style = "auto"
documentation_length = "full"
line_endings = "LF" # also "CR", "CRLF", "Native"

############################# Codegen Options ##################################

style = "both"
sort_by = "Name" # default for `fn.sort_by` and `const.sort_by`
usize_is_size_t = true


[export]
include = []
exclude = []
item_types = []
renaming_overrides_prefixing = false

[fn]
rename_args = "None"
args = "auto"
sort_by = "Name"

[struct]
rename_fields = "None"
derive_constructor = false
derive_eq = false
derive_neq = false
derive_lt = false
derive_lte = false
derive_gt = false
derive_gte = false

[enum]
rename_variants = "None"
add_sentinel = false
prefix_with_name = false
derive_helper_methods = false
derive_const_casts = false
derive_mut_casts = false
derive_tagged_enum_destructor = false
derive_tagged_enum_copy_constructor = false
enum_class = true
private_default_tagged_enum_constructor = false

[const]
allow_static_const = true
allow_constexpr = false
sort_by = "Name"

[macro_expansion]
bitflags = false

############## Options for How Your Rust library Should Be Parsed ##############

[parse]
parse_deps = false
exclude = []
clean = false
extra_bindings = []

[parse.expand]
crates = []
all_features = false
default_features = true
features = []

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

現在我們嘗試將他轉換為 C 語言的標頭檔案吧, 使用:

cbindgen --config cbindgen.toml --crate rust-ffi-backend --output target/release/rust-ffi-backend.h

1

可以在 target/release/rust-ffi-backend-ffi.h 檔案內看到 cbindgen 問我們生成的 C 語言標頭檔案了,他可能長下面這個樣子:

/* Text to put at the beginning of the generated file. Probably a license. */

typedef unsigned int uint32_t;
uint32_t count_add_self();

1
2
3
4

Flutter 前端

現在我們來完成基於 Flutter 的前端桌面程式,首先新建立一個 flutter 工程:

flutter create flutter_frontend

1

開啟它,我們就能看到預設的計數器小程式了,先不著急修改程式,我們先把我們的 RUST 後端匯入進來。
先來匯入 RUST 後端程式

新建一個名叫 rust-ffi 的資料夾存放 RUST 的庫和標頭檔案:

mkdir rust-ffi

1

將剛才生成的檔案都拷貝過來:

cp ../rust-ffi-backend/target/release/rust-ffi-backend.so rust-ffi/
cp ../rust-ffi-backend/target/release/rust-ffi-backend.h rust-ffi/

1
2

現在,我們要依賴 dart 官方的一個名為 ffigen 的程式,它可以幫助我們把 C ABI 的標頭檔案生成 dart 源程式。

首先編輯 pubspec.yaml 檔案,在最後新增下面內容:

ffigen:
output: 'lib/rust-ffi-backend.dart'
headers:
entry-points:
- 'rust-ffi/rust-ffi-backend.h'

1
2
3
4
5

然後使用工具生成 dart 檔案:

dart run ffigen

1

dart 的 ffigen 工具同樣需要依賴 LLVM 等程式,如果報錯請詳細檢視錯誤原因,安裝必要的元件。

這是我們看到在 lib 資料夾下應該生成了我們的 rust-ffi-backend.dart 檔案,它可能是長這樣的:

// AUTO GENERATED FILE, DO NOT EDIT.
//
// Generated by `package:ffigen`.
import 'dart:ffi' as ffi;

class NativeLibrary {
/// Holds the symbol lookup function.
final ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
_lookup;

/// The symbols are looked up in [dynamicLibrary].
NativeLibrary(ffi.DynamicLibrary dynamicLibrary)
: _lookup = dynamicLibrary.lookup;

/// The symbols are looked up with [lookup].
NativeLibrary.fromLookup(
ffi.Pointer<T> Function<T extends ffi.NativeType>(String symbolName)
lookup)
: _lookup = lookup;

int count_add_self(
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Uint32)>> process,
) {
return _count_add_self(
process,
);
}

late final _count_add_selfPtr = _lookup<
ffi.NativeFunction<
ffi.Uint32 Function(
ffi.Pointer<
ffi.NativeFunction<ffi.Void Function(ffi.Uint32)>>)>>(
'count_add_self');
late final _count_add_self = _count_add_selfPtr.asFunction<
int Function(
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Uint32)>>)>();

看到 ffigen 為我們生成了一個 NativeLibrary 類,它擁有一個 count_add_self 方法,對應就是我們 RUST 中的同名方法。

製作 Flutter 前端程式呼叫 RUST

接下來我們就來使用它。

開啟 main.dart 檔案,找到 _MyHomePageState 類,首先引入我們的庫,將我們的庫作為一個類的成員使用,在 class 內新增如下內容:

NativeLibrary nativelib =
NativeLibrary(DynamicLibrary.open('rust-ffi/rust-ffi-backend.so'));

1
2

新增好以後,Flutter 就會在每次初始化 _MyHomePageState 類是載入我們的名為 rust-ffi-backend.so 動態連結庫了。

找到 _incrementCounter 這個函式,可以看到如下內容:

void _incrementCounter() async {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.

_counter++;
});

將其中的 _counter++ 這行程式碼,替換為如下程式碼:

_counter = nativelib.count_add_self();

1

這樣,在 setState 方法中就不會在直接將計數自增,而是會呼叫我們 RUST 中的方法將 RUST 中的全域性變數 COUNT 自增,再返回 COUNT 的值,也就是說,實際上程式碼到 RUST 中兜了一圈。

現在執行我們的 Flutter 工程:

flutter run

1

可以看到,每當我們點選一下,計數自增 2, 說明是呼叫到了 RUST 的函式,實現了 Flutter 和 RUST 的結合。
這裡記錄一些複雜的用法

先寫到這吧,後面補上。
————————————————
版權宣告:本文為CSDN博主「楓安Maplean」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本宣告。
原文連結:https://blog.csdn.net/hyklose/article/details/124482491