Android版のFlutterからRustを呼び出す

Android版のFlutterからRustの関数を呼び出す最低限のメモ。 それらしい記事はいくつかあるものの、理解が出来てないのかバージョンの違いからか中々上手く行かなかったので。 Fluttter, Android, Rust等々の作法として正しいかどうかは正直分からないし、少し前まではDartから直接FFIが扱えなかったのでJNI経由だったようだ。

以下参考にしたページ。

環境

  • Zephyrus GX502
  • Zenfone 6

ツールバージョン

  • Android
    • Studio 3.6.2
    • SDK 10.0 29 4
    • Emulator 30.0.5
    • SDK Platform Tools 29.0.6
    • SDK Tools 26.1.1
  • Dart 192.7761
  • Flutter 45.1.1
  • Rust 1.41.0
  • rustup 1.21.1

インストール

Flutter

普通にインストール。

  1. Android Studio
  2. Flutter
  3. Android NDK
  4. Install and configure the NDK and CMake  |  Android Developers
    • SDK Manager -> SDK Tools -> NDKインストール

Rust

rustupをインストールしrustのandroid関連パッケージをインストールする。

curl https://sh.rustup.rs -sSf | sh
rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
cargo install cargo-ndk

新規Flutterプロジェクト作成

  1. Start a New Flutter project選択
  2. Flutter Application選択
  3. New Flutter Application
    • Project name : flutter_dart_ffi_rust
    • Flutter SDK path : /home/username/.local/flutter
    • Project location : /home/username/AndroidStudioProjects/flutter_dart_ffi_rust
    • Description : Flutter Dart FFI Rust
    • Package name : com.example.flutterdartffirust
    • AndroidX : on
    • Platform channel language
      • Include Kotlin suppport for Android code : on
      • Include Swift support for iOS code : off

新規Rustライブラリ作成

Flutterプロジェクトの中ににRustのライブラリを作成する。

cd flutter_dart_ffi_rust && mkdir rust && cd rust
cargo init --name rust_sample --lib

src/lib.rsを書き換えてC ABIの関数echoを作る。

use std::ffi::{CStr, CString};
use std::os::raw::c_char;

#[no_mangle]
pub unsafe extern "C" fn echo(to: *const c_char) -> *mut c_char {
    let c_str = CStr::from_ptr(to);
    let s = match c_str.to_str() {
        Ok(s) => s,
        Err(_) => "Err",
    };

    CString::new(format!("From Rust {}", s)).unwrap().into_raw()
}

rust/build.sh ビルドスクリプト。 ローカルのシミュレータやAndroid実機等で動かすため複数のプロセッサ向けにコンパイルする。

export ANDROID_NDK_HOME=$HOME/Android/Sdk/ndk/21.0.6113669

for target in aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
do
  cargo ndk --target ${target} --android-platform 22 -- build --release
done

rust/install.sh インストールスクリプト。 最終的な実行時に、共有ライブラリが参照できる場所は具体的にどこなのか作法としてよく分からないが、JNIと同様Flutterの android/app/src/main/jniLibs/ 以下に置いて動いた。

INSTALL_PATH=../android/app/src/main/jniLibs
INSTALL_FILE=librust_sample.so

mkdir -p ${INSTALL_PATH}/arm64-v8a
mkdir -p ${INSTALL_PATH}/armeabi-v7a
mkdir -p ${INSTALL_PATH}/x86
mkdir -p ${INSTALL_PATH}/x86_64

cp target/aarch64-linux-android/release/${INSTALL_FILE} ${INSTALL_PATH}/arm64-v8a/
cp target/armv7-linux-androideabi/release/${INSTALL_FILE} ${INSTALL_PATH}/armeabi-v7a/
cp target/i686-linux-android/release/${INSTALL_FILE} ${INSTALL_PATH}/x86/
cp target/x86_64-linux-android/release/${INSTALL_FILE} ${INSTALL_PATH}/x86_64/

Flutter側修正

pubspec.yamldependencies:ffiを追加する。

ffi: ^0.1.3

lib/main.dart からRustのecho関数を呼び出す。 ffiのインポートを追加し、dll読み込み、呼び出す関数の型定義、echo関数の生成(?)を書く。

import 'dart:ffi';
import 'package:flutter/material.dart';
import 'package:ffi/ffi.dart';

final dll = DynamicLibrary.open('librust_sample.so');
typedef Echo = Pointer<Utf8> Function(Pointer<Utf8> to);
final echo = dll.lookup<NativeFunction<Echo>>('echo').asFunction<Echo>();

void main() => runApp(MyApp());
// 略

lib/main.dart'Flutter Demo Home Page' を関数呼び出しで置き換える。

// 略
home: MyHomePage(title: Utf8.fromUtf8(echo(Utf8.toUtf8('dummy')))),
// 略

実行

Zenfone6上で実行出来た。

f:id:gos-k:20200426180019j:plain