Flutter->`Flutter` 通过`ffi`调用`Rust`编译生成的产物.so文件(Android)和.a文件(iOS)接口方法

flutter_rust_ffi

Flutter 通过ffi调用Rust编译生成的产物.so文件(Android)和.a文件(iOS)接口方法;

拾用本文您将获取以下技能:

  • Rust编译.so文件的能力;
  • Rust编译.a文件的能力;
  • Flutter调用.so文件的能力;
  • Flutter调用.a文件的能力;

附加Buff:

  • Flutter环境安装指南;
  • Rust环境安装指南;
  • Android不同架构(v7a/v8a)的.so文件加载方式;
  • iOS不同设备(真机/模拟器)的.a文件加载方式;

本文环境

  • 开发电脑: Apple M3 Max
  • Flutter IDE: Android Studio Koala | 2024.1.1 Patch 2
  • iOS IDE: Xcode Version 15.4
  • Rust IDE: RustRover 2024.1.7
  • Flutter: Flutter 3.24.0 / Dart 3.5.0
  • Rust: rustc 1.80.1 / rustup 1.27.1

环境准备

使用Rust编译.so

工程准备

使用Rust IDE创建一个Library的工程.

Cargo.toml文件中配置库的类型.

[lib]的描述说明可以参考: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#library

  • cdylib用于输出.so
  • staticlib用于输出.a

如果您还想了解更多类型可以参考: https://doc.rust-lang.org/reference/linkage.html

之后在src/lib.rs文件里面写上非常牛逼自信的高级算法.

工程准备就绪之后, 就可以着手编译了.

安装编译链

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

  • aarch64-linux-android 用于输出arm64-v8a的.so文件
  • armv7-linux-androideabi 用于输出armeabi-v7a的.so文件

您可以通过rustup target list查看所有支持的工具链.

安装编译工具

cargo install cargo-ndk

  • cargo-ndk 用来编译so文件

编译

cargo ndk -t armeabi-v7a -t arm64-v8a build --release

关于cargo ndk更多用法可以参考: https://github.com/bbqsrc/cargo-ndk

  • arm64-v8a平台的so文件输出在target/aarch64-linux-android/release/xxx.so
  • armeabi-v7a平台的so文件输出在target/armv7-linux-androideabi/release/xxx.so

之后将产物分别复制到Flutter工程中的android/src/main/jniLibs/arm64-v8aandroid/src/main/jniLibs/armeabi-v7a这样在Android平台上,就会根据CPU的架构自动加载对应的so文件, 这一点在iOS平台上需要手动处理, 在介绍iOS时, 会提及.

到这为止, Android平台的产物so文件就已经输出了. 接下来编译iOS.

使用Rust编译.a

工程准备

与上述一致.

安装编译链

rustup target add aarch64-apple-ios aarch64-apple-ios-sim

  • aaarch64-apple-ios 用于输出iPhone真机的.a文件
  • aarch64-apple-ios-sim 用于输出iPhone模拟器的.a文件

您可以通过rustup target list查看所有支持的工具链.

安装编译工具

cargo install cargo-lipo

  • cargo-lipo 用来编译.a文件

编译

cargo lipo --targets aarch64-apple-ios --release
cargo lipo --targets aarch64-apple-ios-sim --release

这里要分开2个命令编译不同的文件.

关于cargo lipo更多用法可以参考: https://github.com/TimNN/cargo-lipo

  • iPhone真机的a文件输出在target/aarch64-apple-ios/release/xxx.a
  • iPhone模拟器的a文件输出在target/aarch64-apple-ios-sim/release/xxx.a

之后将产物分别复制到Flutter工程中的ios/iphoneos(iPhone真机)和ios/iphonesimulator(iPhone模拟器).

之后在iOS工程中的xxx.podspec文件中加入:

...
s.user_target_xcconfig = {
'OTHER_LDFLAGS' => '-force_load ${PODS_ROOT}/../.symlinks/plugins/flutter_rust_ffi/ios/${PLATFORM_NAME}/librust_api_test.a'
}
...
  • ${PLATFORM_NAME}是用来自动加载iPhone真机iPhone模拟器的关键.

如果要区分i386arm64架构, 可以使用${ARCHS_STANDARD_INCLUDING_64_BIT}环境变量.

之后在Flutter工程中的example/ios文件夹中使用pod install命令.

到这为止, iOS平台的产物a文件就已经输出了.

Rust导出ffi

上述生成的产物还不支持ffi调用, 所以这里阐述一下.

传统的导出ffi的方式extern fn比较繁琐, 并且不易于生成.h头文件.

参考: https://doc.rust-lang.org/nomicon/ffi.html

这里使用safer_ffi库(https://getditto.github.io/safer_ffi/)导出`ffi`

首先在Cargo.toml文件中加入safer-ffi依赖:

toml 复制代码
[dependencies]
safer-ffi = "0.1.12"

并且指定特性:

toml 复制代码
[features]
headers = ["safer-ffi/headers"]

其次在需要导出的方法中使用#[ffi_export]宏:

rust 复制代码
#[ffi_export]
pub fn test_bool(value: bool) -> bool {
    !value
}

最后配置一下生成头文件名的方法generate_headers:

rust 复制代码
#[test]
#[cfg(feature = "headers")]
fn generate_headers() -> std::io::Result<()> {
    safer_ffi::headers::builder()
        .to_file("rust_api_test.h")?
        .generate()
}

之后就可以使用命令运行这个方法generate_headers生成对应的头文件了:

cargo test --lib generate_headers --features headers

Flutter导入ffi

使用Flutter创建一个FFI Plugin工程, 既可以获得相应的模板代码.

有了.h头文件, Flutter就可以借助ffigen工具创建对应的dart绑定类

dart run ffigen --config ffigen.yaml

ffigen.yaml配置文件内容, 请参考: https://pub.dev/packages/ffigen 文档. 很简单, 只需要指定对应的.h文件位置即可.

这里需要注意的是, Flutter在加载.so文件或.a文件时, 有所差异.

  • 加载.so文件直接使用默认的DynamicLibrary.open('lib$_libName.so');方法即可.
  • 但是加载.a文件则需要使用DynamicLibrary.executable();方法.
dart 复制代码
final DynamicLibrary _dylib = () {
  if (Platform.isMacOS || Platform.isIOS) {
    //return DynamicLibrary.open('$_libName.framework/$_libName');
    return DynamicLibrary.executable();
  }
  if (Platform.isAndroid || Platform.isLinux) {
    return DynamicLibrary.open('lib$_libName.so');
  }
  if (Platform.isWindows) {
    return DynamicLibrary.open('$_libName.dll');
  }
  throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();

到这里为止, Flutter就已经可以在Android平台上愉快的调用ffi了, 而不需要额外的配置.

但是, 在iOS平台上, 会有问题, 如下:

  • IOS Failed to lookup symbol (dlsym(RTLD_DEFAULT, test_func): symbol not found)

为了解决这个问题, 您需要在Xcode中进行如下配置:

  • 导航到TARGETS->Runner->Build Settings->Linking - GeneralDead Code Stripping配置改成No(这是iOS移除未使用的代码用的)
  • 在同级的Ohter Linker Flags中加入-all_load(这是加载所有符号表用的)

这里说明一点: -all_load和上文中的-force_load xxx.a作用是一致的,自行取其一配置即可.

配置完成之后, rebuildFlutter在iOS平台上也可以愉快的调用ffi了.

文中源码有所省略, 文末有开源代码仓库地址, 欢迎食用.

至此文章就结束了!


群内有各(pian)种(ni)各(jin)样(qun)的大佬,等你来撩.

联系作者

点此QQ对话 该死的空格 点此快速加群

开源地址

相关推荐
许野平21 分钟前
Rust: 利用 chrono 库实现日期和字符串互相转换
开发语言·后端·rust·字符串·转换·日期·chrono
无极程序员30 分钟前
PHP常量
android·ide·android studio
萌面小侠Plus2 小时前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机
慢慢成长的码农2 小时前
Android Profiler 内存分析
android
大风起兮云飞扬丶2 小时前
Android——多线程、线程通信、handler机制
android
君蓦2 小时前
Flutter 本地存储与数据库的使用和优化
flutter
L72562 小时前
Android的Handler
android
清风徐来辽2 小时前
Android HandlerThread 基础
android
HerayChen3 小时前
HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac
android·macos·智能手机