Flutter呼び出しRustコード操作ガイド

ここに画像の説明を挿入

Rust と Flutter を使用したガジェットの開発に関する前回の記事では、Rust コードを使用して簡単な WebSocket 送信関数を実装しました。Rust ライブラリのパッケージ化とダブルエンドの使用は、Rust ライブラリのクロスコンパイルとAndroid と iOS の使用でも紹介されています。

今日は引き続き、以前の WebSocket コードを例として使用して、Flutter プロジェクトでの使用方法を紹介します。

準備

この記事の主役はflutter_rust_bridgeFlutterです。これは、および のためのRust高度なメモリセーフ バインディング ジェネレーターです。Flutter / Dartこのライブラリは、呼び出し機能を支援するコード ジェネレーターにすぎませんRust手動で記述するのではなく、定型コードを生成するだけです。

まず、Rust コードを Flutter プロジェクトのルート ディレクトリに配置するか、実行してcargo new --lib新しい Rust クレートを作成します。完了後のプロジェクト構造は次のようになります。

├── android
├── ios
├── lib
├── linux
├── macos
├── $crate
│   ├── Cargo.toml
│   └── src
├── test
├── web
└── windows

注: クレートのルート ディレクトリを他のプロジェクトと同じレベルに設定すると、構成プロセスが簡素化されます。

以前の Rust コードを少し変更します (コードは lib.rs に直接書かないでください。そうしないと、生成されたファイルがインポート パッケージを取得できなくなります)。

ここに前のコードapi.rsを入れます。

use std::collections::HashMap;
use std::sync::Mutex;
use ws::{
    
    connect, Handler, Sender, Handshake, Result, Message, CloseCode, Error};
use ws::util::Token;


lazy_static! {
    
    
    static ref DATA_MAP: Mutex<HashMap<String, Sender>> = {
    
    
        let map: HashMap<String, Sender> = HashMap::new();
        Mutex::new(map)
    };
}

struct Client {
    
    
    sender: Sender,
    host: String,
}

impl Handler for Client {
    
    
    fn on_open(&mut self, _: Handshake) -> Result<()> {
    
    
        DATA_MAP.lock().unwrap().insert(self.host.to_owned(), self.sender.to_owned());
        Ok(())
    }

    fn on_message(&mut self, msg: Message) -> Result<()> {
    
    
        println!("<receive> '{}'. ", msg);
        Ok(())
    }

    fn on_close(&mut self, _code: CloseCode, _reasonn: &str) {
    
    
        DATA_MAP.lock().unwrap().remove(&self.host);
    }

    fn on_timeout(&mut self, _event: Token) -> Result<()> {
    
    
        DATA_MAP.lock().unwrap().remove(&self.host);
        self.sender.shutdown().expect("shutdown error");
        Ok(())
    }

    fn on_error(&mut self, _err: Error) {
    
    
        DATA_MAP.lock().unwrap().remove(&self.host);
    }

    fn on_shutdown(&mut self) {
    
    
        DATA_MAP.lock().unwrap().remove(&self.host);
    }

}

pub fn websocket_connect(host: String) {
    
    
    if let Err(err) = connect(host.to_owned(), |out| {
    
    
        Client {
    
    
            sender: out,
            host: host.to_owned(),
        }
    }) {
    
    
        println!("Failed to create WebSocket due to: {:?}", err);
    }
}

pub fn send_message(host: String, message: String) {
    
    
    let binding = DATA_MAP.lock().unwrap();
    let sender = binding.get(&host.to_owned());
    
    match sender {
    
    
        Some(s) => {
    
    
            if s.send(message).is_err() {
    
    
                println!("Websocket couldn't queue an initial message.")
            };
        } ,
        None => println!("None")
    }
}

pub fn websocket_disconnect(host: String) {
    
    
    DATA_MAP.lock().unwrap().remove(&host.to_owned());
}

API

mod api;
#[macro_use]
extern crate lazy_static;

Cargo.toml構成は次のとおりです。

[package]
name = "rust_demo"
version = "0.1.0"
edition = "2021"
publish = false

[lib]
name = "rust_demo"
crate-type = ["staticlib", "cdylib"]

[profile.release]
lto = true
opt-level = 'z'
strip = true
codegen-units = 1
# panic = 'abort'

[dependencies]
ws = "0.9.2"
lazy_static = "1.4.0"
flutter_rust_bridge = "=1.77.1"
flutter_rust_bridge_macros = "=1.77.1"

[build-dependencies]
flutter_rust_bridge_codegen = "=1.77.1"

Flutterでの設定はpubspec.yaml以下の通りです。

dependencies:
  flutter_rust_bridge: 1.77.1
  ffi: ^2.0.1

dev_dependencies:
  ffigen: ^8.0.2

ここに記載されているバージョンはflutter_rust_bridge一貫している必要があります。ここでは現在 1.77.1 を使用しています。次に、Rust プロジェクトで実行します。

cargo install flutter_rust_bridge_codegen
# 如果为iOS或MacOS应用构建
cargo install cargo-xcode
  • flutter_rust_bridge_codegen、Rust-Dart グルー コードのコアを生成します。
  • ffigen、C ヘッダー ファイルから Dart コードを生成します/
  • LLVM をインストールするには、「LLVM のインストール」を参照してください。ffigen はそれを使用します。
  • (オプション) cargo-xcodeIOS および MacOS 用の Xcode プロジェクトを生成する場合。

上記の準備作業が完了したら、Flutter プロジェクトでコマンドを実行し、コードを正常に貼り付けることができます。

flutter_rust_bridge_codegen -r native/src/api.rs -d lib/ffi/rust_ffi.dart -c ios/Runner/bridge_generated.h
  • native/src/api.rsRust コードのパス。
  • lib/ffi/rust_ffi.dartダーツコードのパスを生成します。
  • ios/Runner/bridge_generated.hRust ライブラリによってエクスポートされたすべてのシンボルをリストする C ヘッダー ファイルを作成します。これを使用して、Xcode がシンボルを削除しないようにする必要があります。

Androidの設定

最初にインストールするとcargo-ndk、追加の構成を行わずにコードが適切な JNI にコンパイルされます。前回の記事では、.cargo/configclang リンカーのパスを手動で構成しました。むしろ面倒ですが、この操作を簡略化するのがこのプラグインです。

インストールコマンド:

// ndk低于22
cargo install cargo-ndk --version 2.6.0
// ndk高于22
cargo install cargo-ndk

前回の記事で説明したように、Android へのクロスコンパイルにはいくつかの追加コンポーネントが必要です。

rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android

次に、android/app/build.gradle次の行を の最後に追加します。

[
        Debug: null,
        Profile: '--release',
        Release: '--release'
].each {
    
    
    def taskPostfix = it.key
    def profileMode = it.value
    tasks.whenTaskAdded {
    
     task ->
        if (task.name == "javaPreCompile$taskPostfix") {
    
    
            task.dependsOn "cargoBuild$taskPostfix"
        }
    }
    tasks.register("cargoBuild$taskPostfix", Exec) {
    
    
        workingDir "../../native"
        environment ANDROID_NDK_HOME: "$ANDROID_NDK"
        commandLine 'cargo', 'ndk',
                // the 2 ABIs below are used by real Android devices
                '-t', 'armeabi-v7a',
                '-t', 'arm64-v8a',
                '-o', '../android/app/src/main/jniLibs', 'build'
        if (profileMode != null) {
    
    
            args profileMode
        }
    }
}
  • ../../nativeRustのコードパスです。
  • ANDROID_NDKandroid/gradle.properties構成されているのは NDK パスです。
  • Android が実行されるたびに、Rust コードがパッケージ化され、android/app/src/main/jniLibsその中に so ファイルが配置されます。したがって、Rust コードが変更されていない場合は、リリース ファイルの生成後にここでコードをコメント アウトできます。
ANDROID_NDK=/Users/weilu/android/android-sdk-macosx/ndk/21.4.7075529

iOSの設定

クロスコンパイル コンポーネントをインストールします。

rustup target add aarch64-apple-ios x86_64-apple-ios

次に、それをRustプロジェクトディレクトリで実行しますcargo xcode実行後、xcodeprojsuffixフォルダーが生成されます。他の Xcode プロジェクトにインポートするために使用できます。
画像の説明を追加してください
Xcode で開きios/Runner.xcodeproj、メニューをクリックしてFile ---> Add Files to "Runner"サブxxx.xcodeprojプロジェクトとして追加します。

  • Runnerルート プロジェクトをクリックし、 Build Phases[タブ] の下のTarget Dependenciesプラス記号をクリックして$crate-staticlibファイルを追加します。
    画像の説明を追加してください
  • 次に、以下を展開し、Link Binary With Librariesプラス記号をクリックして$crate_static.aIOS の lib ファイルを追加します。
    画像の説明を追加してください
    完成後のイメージは以下の通りです。
    画像の説明を追加してください

最初に生成されたヘッダー ファイルをバインドしますbridge_generated.h

追加 ios/Runner/Runner-Bridging-Header.h: bridge_generated.h

#import "GeneratedPluginRegistrant.h"
#import "bridge_generated.h"

ios/Runner/AppDelegate.swift追加dummy_method_to_enforce_bundling():

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    
    
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    
    
    let dummy = dummy_method_to_enforce_bundling()
    print(dummy)
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

フラッターコール

ここまでで設定作業は完了です。呼び出し方法を見てみましょう。まず、メソッド呼び出しクラスを単純にカプセル化します。

import 'dart:ffi';
import 'dart:io';

import 'package:flutter_ffi/ffi/rust_ffi.dart';

class NativeFFI {
    
    
  NativeFFI._();

  static DynamicLibrary? _dyLib;

  static DynamicLibrary get dyLib {
    
    
    if (_dyLib != null) return _dyLib!;

    if (Platform.isIOS) {
    
    
      _dyLib = DynamicLibrary.process();
    } else if (Platform.isAndroid) {
    
    
      _dyLib = DynamicLibrary.open('librust_demo.so');
    } else {
    
    
      throw Exception('DynamicLibrary初始化失败');
    }

    return _dyLib!;
  }
}

class NativeFun {
    
    
  static final _ffi = RustDemoImpl(NativeFFI.dyLib);

  static Future<void> websocketConnect(String host) async {
    
    
    return await _ffi.websocketConnect(host: host);
  }

  static Future<void> sendMessage(String host, String message) async {
    
    
    return await _ffi.sendMessage(host: host, message: message);
  }

  static Future<void> websocketDisconnect(String host) async {
    
    
    return await _ffi.websocketDisconnect(host: host);
  }
}

使用する場合は、NativeFun.xxx()メソッドを直接呼び出しても問題ありません。


上記のサンプル コードをGithubに送信しました。必要に応じて実行して表示できます。お役に立った場合は、「いいね!」を押して保存してください〜また来月!

参考

おすすめ

転載: blog.csdn.net/qq_17766199/article/details/131141194