Flutter和Rust如何优雅的交互

前言

文章的图片链接都是在github上,可能需要...你懂得;本文含有大量关键步骤配置图片,强烈建议在合适环境下阅读

Flutter直接调用C层还是蛮有魅力,想想你练习C++,然后直接能用flutter在上层展示出效果,是不是就有大量练手的机会了,逻辑反手就用C++,Rust去写,给后面的接盘侠留下一座壮丽的克苏鲁神山,供其瞻仰

上面只是开个玩笑,目前flutter ffi的交互,主要是为了和底层交互的统一,还能直接使用到大量宝藏一样的底层库

目前ffi的同步调用还是比较可以,异步交互有办法去解决,但是使用起来比较麻烦

  • 有兴趣的可以查看下面异步消息通信模块中贴的issue

Flutter和Rust的交互

  • flutter_rust_bridge库给了一个很不错的解决方案
  • 主要是他能很轻松的实现异步交互!

本文是循序渐进式,比较全面的介绍了flutter的ffi使用,ffigen使用,最后才是rust交互介绍;如果对ffi和ffigen不太关心,也可直接阅读rust交互内容

FFI交互方式

配置

Android

  • 需要先配置ndk
# mac ndk.dir=/Users/***/Develop/SDK/android_sdk/ndk/21.3.6528147 # windows ndk.dir=F:\SDK\AndroidSDK\ndk\21.3.6528147 

Flutter和Rust如何优雅的交互

  • 安装下CMake

Flutter和Rust如何优雅的交互

  • 需要在Android的build.gradle里配置下cmake路径
android {     ...      //配置CMakeList路径     externalNativeBuild {         cmake {             path "../lib/native/CMakeLists.txt"         }     } } 

Flutter和Rust如何优雅的交互

  • 因为Windows和Linux都需要用到CMakeLists.txt,先来看下Android的配置
    • Android的比较简单,配置下需要编译的c文件就行了
    • 一个个添加文件的方式太麻烦了,这边直接用native_batch批量添加文件
    • android会指定给定义的项目名上加上libset(PROJECT_NAME "native_fun")生成的名称应该为libnative_fun.so
# cmake_minimum_required 表示支持的 cmake 最小版本 cmake_minimum_required(VERSION 3.4.1)  # 项目名称 set(PROJECT_NAME "native_fun")  # 批量添加c文件 # add_library 关键字表示构建链接库,参数1是链接包名称; 参数2'SHARED'表示构建动态链接库; 参数2是源文件列表 file(GLOB_RECURSE native_batch ../../ios/Classes/native/*) add_library(${PROJECT_NAME} SHARED ${native_batch}) 

可以发现file(GLOB_RECURSE native_batch ../../ios/Classes/native/*)这边路径设置在iOS的Classes文件下,这边是为了方便统一编译native文件夹下的所有c文件,macOS和iOS需要放在Classes下,可以直接编译

但是macOS和iOS没法指定编译超过父节点位置,必须放在Classes子文件夹下,超过这个节点就没法编译

所以这边iOS和macOS必须要维护俩份相同c文件(建个文件夹吧,方便直接拷贝过去);Android,Windows,Linux可以指定到这俩个中的其中之一(建议指定iOS的Classes,避免一些灵异Bug)

  • 效果

Flutter和Rust如何优雅的交互

iOS

  • iOS可以直接编译C文件,需要放在Classes文件夹下

Flutter和Rust如何优雅的交互

  • 效果

Flutter和Rust如何优雅的交互

macOS

  • macOS也可以直接编译C文件,需要放在Classes文件夹下

Flutter和Rust如何优雅的交互

  • 效果

Flutter和Rust如何优雅的交互

Windows

  • windows下的CMakeLists.txt里面指定了lib/native下面的统一CMakeLists.txt配置
# cmake_minimum_required 表示支持的 cmake 最小版本 cmake_minimum_required(VERSION 3.4.1)  # 项目名称 set(PROJECT_NAME "libnative_fun")  # 批量添加cpp文件 # add_library 关键字表示构建链接库,参数1是链接包名称; 参数2'SHARED'表示构建动态链接库; 参数2是源文件列表 file(GLOB_RECURSE native_batch ../../ios/Classes/native/*) add_library(${PROJECT_NAME} SHARED ${native_batch})  # Windows 需要把dll拷贝到bin目录 # 动态库的输出目录 set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$<$<CONFIG:DEBUG>:Debug>$<$<CONFIG:RELEASE>:Release>") # 安装动态库的目标目录 set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") # 安装动态库,到执行目录 install(FILES "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${PROJECT_NAME}.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) 

Flutter和Rust如何优雅的交互

  • 这边可以将Android和Windows的配置统一下,加下判断即可
# cmake_minimum_required 表示支持的 cmake 最小版本 cmake_minimum_required(VERSION 3.4.1)  # 项目名称 if (WIN32)     set(PROJECT_NAME "libnative_fun") else()     set(PROJECT_NAME "native_fun") endif()  # 批量添加c文件 # add_library 关键字表示构建链接库,参数1是链接包名称; 参数2'SHARED'表示构建动态链接库; 参数2是源文件列表 file(GLOB_RECURSE native_batch ../../ios/Classes/native/*) add_library(${PROJECT_NAME} SHARED ${native_batch})  # Windows 需要把dll拷贝到bin目录 if (WIN32)     # 动态库的输出目录     set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$<$<CONFIG:DEBUG>:Debug>$<$<CONFIG:RELEASE>:Release>")     # 安装动态库的目标目录     set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")     # 安装动态库,到执行目录     install(FILES "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${PROJECT_NAME}.dll" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endif() 
  • 说明下,Windows这边必须将生成的dll拷贝到bin目录下,才能调用
    • 所以cmake里面,最后那段Windows的特有代码是必须要写的

Flutter和Rust如何优雅的交互

  • 效果

Flutter和Rust如何优雅的交互

交互

  • 通用加载:NativeFFI.dynamicLibrary
class NativeFFI {   NativeFFI._();    static DynamicLibrary? _dyLib;    static DynamicLibrary get dynamicLibrary {     if (_dyLib != null) return _dyLib!;      if (Platform.isMacOS || Platform.isIOS) {       _dyLib = DynamicLibrary.process();     } else if (Platform.isAndroid) {       _dyLib = DynamicLibrary.open('libnative_fun.so');     } else if (Platform.isWindows) {       _dyLib = DynamicLibrary.open('libnative_fun.dll');     } else {       throw Exception('DynamicLibrary初始化失败');     }      return _dyLib!;   } } 

Flutter同步调用Native

  • dart
/// 俩数相加 int ffiAddSyncInvoke(int a, int b) {   final int Function(int x, int y) nativeAdd = NativeFFI.dynamicLibrary       .lookup<NativeFunction<Int32 Function(Int32, Int32)>>("twoNumAdd")       .asFunction();    return nativeAdd(a, b); } 
  • native
#include <stdint.h>  #ifdef WIN32 #define DART_API extern "C" __declspec(dllexport) #else #define DART_API extern "C" __attribute__((visibility("default"))) __attribute__((used)) #endif  DART_API int32_t twoNumAdd(int32_t x, int32_t y){     return x + y; } 
  • 效果

Flutter和Rust如何优雅的交互

Native同步触发Flutter回调

  • dart
/// 传递的回调 typedef _NativeCallback = Int32 Function(Int32 num);  /// Native方法 typedef _NativeSyncCallback = Void Function(   Pointer<NativeFunction<_NativeCallback>> callback, );  /// Dart结束回调: Void和void不同,所以要区分开 typedef _DartSyncCallback = void Function(   Pointer<NativeFunction<_NativeCallback>> callback, );  /// 必须使用顶层方法或者静态方法 /// macos端可以打印出native层日志, 移动端只能打印dart日志 int _syncCallback(int num) {   print('--------');   return num; }  /// 在native层打印回调传入的值 void ffiPrintSyncCallback() {   final _DartSyncCallback dartSyncCallback = NativeFFI.dynamicLibrary       .lookup<NativeFunction<_NativeSyncCallback>>("nativeSyncCallback")       .asFunction();    // 包装传递的回调   var syncFun = Pointer.fromFunction<_NativeCallback>(_syncCallback, 0);   dartSyncCallback(syncFun); } 
  • native
#include <stdint.h> #include <iostream>  #ifdef WIN32 #define DART_API extern "C" __declspec(dllexport) #else #define DART_API extern "C" __attribute__((visibility("default"))) __attribute__((used)) #endif  using namespace std;  // 定义传递的回调类型 typedef int32_t (*NativeCallback)(int32_t n);  DART_API void nativeSyncCallback(NativeCallback callback) {     // 打印     std::cout << "native log callback(666) = " << callback(666) << std::endl; } 
  • 效果

Flutter和Rust如何优雅的交互

异步消息通信

说明

异步交互的写法有点复杂,可以查看下面的讨论

异步通信需要导入额外c文件用作通信支持,但是如果你的iOS项目是swift项目,无法编译这些额外c文件

  • 这些c文件我是封装在插件里,没想到办法怎么建立桥接
  • 如果是OC项目,就可以直接编译

目前来看

  • Android和iOS可以编译额外的消息通信的c文件
  • windows和macos试了,都没法编译,麻了

使用

  • dart
import 'dart:async'; import 'dart:ffi'; import 'dart:isolate';  import 'package:flutter/material.dart'; import 'package:flutter_ffi_toolkit/src/native_ffi.dart';  ReceivePort? _receivePort; StreamSubscription? _subscription;  void _ensureNativeInitialized() {   if (_receivePort == null) {     WidgetsFlutterBinding.ensureInitialized();     final initializeApi = NativeFFI.dynamicLibrary.lookupFunction<         IntPtr Function(Pointer<Void>),         int Function(Pointer<Void>)>("InitDartApiDL");     if (initializeApi(NativeApi.initializeApiDLData) != 0) {       throw "Failed to initialize Dart API";     }      _receivePort = ReceivePort();     _subscription = _receivePort!.listen(_handleNativeMessage);     final registerSendPort = NativeFFI.dynamicLibrary.lookupFunction<         Void Function(Int64 sendPort),         void Function(int sendPort)>('RegisterSendPort');     registerSendPort(_receivePort!.sendPort.nativePort);   } }  void _handleNativeMessage(dynamic address) {   print('---------native端通信,地址: $address');   Pointer<Int32> point = Pointer<Int32>.fromAddress(address);   print('---------native端通信,指针: $point');   dynamic data = point.cast();   print('---------native端通信,cast: $data'); }  void ffiAsyncMessage(int a) {   _ensureNativeInitialized();   final void Function(int x) asyncMessage = NativeFFI.dynamicLibrary       .lookup<NativeFunction<Void Function(Int32)>>("NativeAsyncMessage")       .asFunction();    asyncMessage(a); }  void dispose() {   // TODO _unregisterReceivePort(_receivePort.sendPort.nativePort);   _subscription?.cancel();   _receivePort?.close(); } 
  • native
// C #include <stdio.h>  // Unix #include <unistd.h> #include <pthread.h>  #include "dart_api/dart_api.h" #include "dart_api/dart_native_api.h"  #include "dart_api/dart_api_dl.h"  // Initialize `dart_api_dl.h` DART_EXPORT intptr_t InitDartApiDL(void* data) {   return Dart_InitializeApiDL(data); }  Dart_Port send_port_;  DART_EXPORT void RegisterSendPort(Dart_Port send_port) {   send_port_ = send_port; }  void *thread_func(void *args) {     printf("thread_func Running on (%p)n", pthread_self());     sleep(2 /* seconds */); // doing something      Dart_CObject dart_object;     dart_object.type = Dart_CObject_kInt64;     dart_object.value.as_int64 = reinterpret_cast<intptr_t>(args);     Dart_PostCObject_DL(send_port_, &dart_object);      pthread_exit(args); }  DART_EXPORT void NativeAsyncMessage(int32_t x) {     printf("NativeAsyncCallback Running on (%p)n", pthread_self());      pthread_t message_thread;     pthread_create(&message_thread, NULL, thread_func, (void *)&x); } 

Flutter和Rust如何优雅的交互

  • 效果

Flutter和Rust如何优雅的交互

ffigen使用

手写这些ffi交互代码,也是件比较麻烦的事,而且每个方法都要写对应的类型转换和相应的硬编码方法名,如果c的某个方法改变参数和方法名,再回去改对应的dart代码,无疑是一件蛋痛的事

flutter提供了一个自动生成ffi交互的代码,通俗的说:自动将c代码生成为对应dart的代码

配置

  • ubuntu/linux

    • 安装 libclangdev: sudo apt-get install libclang-dev
  • Windows

    • 安装 Visual Studio with C++ development support
    • 安装 LLVMwinget install -e --id LLVM.LLVM
  • MacOS

    • 安装 Xcode

    • 安装 LLVM: brew install llvm

  • 引入ffigen

dependencies:   ffigen: ^7.2.0  ffigen:   # 输出生成的文件路径   output: 'lib/src/ffigen/two_num_add.dart'   # 输出的类名   name: NativeLibrary   headers:     # 配置需要生成的文件     entry-points:       - 'ios/Classes/native/ffigen/add.cpp'     # 保证只转换two_num_add.cpp文件,不转换其包含的库文件,建议加上     include-directives:       - 'ios/Classes/native/ffigen/add.cpp' 

生成文件

  • 需要注意:生成的文件位置,需要和指定文件的编译位置保持一致,这样才能编译这些c文件

Flutter和Rust如何优雅的交互

  • ffigen生成命令
dart run ffigen 
  • add.cpp
    • 使用命令生成对应dart文件的时候,方法名前不能加我们定义的DART_API,不然无法生成对应dart文件
    • 编译的时候必须要加上DART_API,不然无法编译该方法
    • 有点无语,有知道能统一处理cpp文件方法的,还请在评论区告知呀
#include <stdint.h>  #ifdef WIN32 #define DART_API extern "C" __declspec(dllexport) #else #define DART_API extern "C" __attribute__((visibility("default"))) __attribute__((used)) #endif  // DART_API int32_t twoNumAddGen(int32_t x, int32_t y){ //     return x + y; // }  int32_t twoNumAddGen(int32_t x, int32_t y){     return x + y; } 
  • 生成的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 twoNumAddGen(     int x,     int y,   ) {     return _twoNumAddGen(       x,       y,     );   }    late final _twoNumAddGenPtr =       _lookup<ffi.NativeFunction<ffi.Int32 Function(ffi.Int32, ffi.Int32)>>(           'twoNumAddGen');   late final _twoNumAddGen =       _twoNumAddGenPtr.asFunction<int Function(int, int)>(); } 

Flutter和Rust如何优雅的交互

使用

  • 通用加载:NativeFFI.dynamicLibrary
class NativeFFI {   NativeFFI._();    static DynamicLibrary? _dyLib;    static DynamicLibrary get dynamicLibrary {     if (_dyLib != null) return _dyLib!;      if (Platform.isMacOS || Platform.isIOS) {       _dyLib = DynamicLibrary.process();     } else if (Platform.isAndroid) {       _dyLib = DynamicLibrary.open('libnative_fun.so');     } else if (Platform.isWindows) {       _dyLib = DynamicLibrary.open('libnative_fun.dll');     } else {       throw Exception('DynamicLibrary初始化失败');     }      return _dyLib!;   } } 
  • 使用
NativeLibrary(NativeFFI.dynamicLibrary).twoNumAddGen(a, b); 
  • 效果

Flutter和Rust如何优雅的交互

rust 交互

使用flutter_rust_bridge:flutter_rust_bridge

下面全平台的配置,我成功编译运行后写的一份详细指南(踩了一堆坑),大家务必认真按照步骤配置~

大家也可以参考官方文档,不过我觉得写的更加人性化,hhhhhh...

准备

创建Rust项目

Flutter和Rust如何优雅的交互

  • Cargo.toml 需要引入三个库:[package]和[lib]中的name参数,请保持一致,此处示例是name = "rust_ffi"
    • [lib]:crate-type =["lib", "staticlib", "cdylib"]
    • [build-dependencies]:flutter_rust_bridge_codegen
    • [dependencies]:flutter_rust_bridge
    • 最新版本查看:https://crates.io/
[package] name = "rust_ffi" version = "0.1.0" edition = "2021"  [lib] name = "rust_ffi" crate-type = ["staticlib", "cdylib"]  [build-dependencies] flutter_rust_bridge_codegen = "=1.51.0"  [dependencies] flutter_rust_bridge = "=1.51.0" flutter_rust_bridge_macros = "=1.51.0" 
  • 写rust代码需要注意下,不要在lib.rs中写代码,不然生成文件无法获取导包

Flutter和Rust如何优雅的交互

Flutter项目

  • flutter项目正常创建就行了

Flutter和Rust如何优雅的交互

  • flutter的pubspec.yaml中需要添加这些库
dependencies:   # https://pub.dev/packages/flutter_rust_bridge   flutter_rust_bridge: 1.51.0   ffi: ^2.0.1  dev_dependencies:   ffigen: ^7.0.0 

命令

  • 需要先安装下代码生成工具
# 必须 cargo install flutter_rust_bridge_codegen # iOS和macOS 必须需要 cargo install cargo-xcode 
  • 安装LLVM

    • ubuntu/linux

      • 安装 libclangdev: sudo apt-get install libclang-dev
    • Windows

      • 安装 Visual Studio with C++ development support
      • 安装 LLVMwinget install -e --id LLVM.LLVM
    • MacOS

      • 安装 Xcode

      • 安装 LLVM: brew install llvm

  • 生成命令

flutter_rust_bridge_codegen -r rust/src/api.rs -d lib/ffi/rust_ffi/rust_ffi.dart  
  • 如果需要iOS和macOS,用下面的命令,说明请参照:配置 ---> iOS / macOS
flutter_rust_bridge_codegen -r rust/src/api.rs -d lib/ffi/rust_ffi/rust_ffi.dart -c ios/Runner/bridge_generated.h -c macos/Runner/bridge_generated.h 
  • 请注意
    • 如果你在flutter侧升级了flutter_rust_bridge版本
    • rust的Cargo.toml也应该对flutter_rust_bridge_codegenflutter_rust_bridge升级对应版本
    • 升级完版本后需要重新跑下该命令
# 自动安装最新版本 cargo install flutter_rust_bridge_codegen # 指定版本 cargo install flutter_rust_bridge_codegen --version 1.51.0 --force 

配置

Android

  • 必须要安装cargo-ndk :它能够将代码编译到适合的 JNI 而不需要额外的配置
cargo install cargo-ndk 
  • 添加cargo的android编译工具,在命令行执行下下述命令
rustup target add aarch64-linux-android rustup target add armv7-linux-androideabi rustup target add x86_64-linux-android rustup target add i686-linux-android 
  • NDK:请使用NDK 22或更早的版本,NDK下载请参考下图

Flutter和Rust如何优雅的交互

  • 需要在gradle.properties配置下
# mac ANDROID_NDK=/Users/***/Develop/SDK/android_sdk/ndk/21.3.6528147 # windows ANDROID_NDK=F:\SDK\AndroidSDK\ndk\21.3.6528147 

Flutter和Rust如何优雅的交互

  • android/app/build.gradle 的最后添加下面几行
    • ANDROID_NDK 就是在上面配置的变量
    • "../../rust":此处请配置自己rust项目文件夹命名
[     new Tuple2('Debug', ''),     new Tuple2('Profile', '--release'),     new Tuple2('Release', '--release') ].each {     def taskPostfix = it.first     def profileMode = it.second     tasks.whenTaskAdded { task ->         if (task.name == "javaPreCompile$taskPostfix") {             task.dependsOn "cargoBuild$taskPostfix"         }     }     tasks.register("cargoBuild$taskPostfix", Exec) {         // Until https://github.com/bbqsrc/cargo-ndk/pull/13 is merged,         // this workaround is necessary.          def ndk_command = """cargo ndk              -t armeabi-v7a -t arm64-v8a -t x86_64 -t x86              -o ../android/app/src/main/jniLibs build $profileMode"""          workingDir "../../rust"         environment "ANDROID_NDK_HOME", "$ANDROID_NDK"         if (org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem.isWindows()) {             commandLine 'cmd', '/C', ndk_command         } else {             commandLine 'sh', '-c', ndk_command         }     } } 

iOS

  • iOS 需要一些额外的交叉编译目标:
# 64 bit targets (真机 & 模拟器): rustup target add aarch64-apple-ios x86_64-apple-ios # New simulator target for Xcode 12 and later rustup target add aarch64-apple-ios-sim 
  • 需要先生成子项目
# 在rust项目下执行该命令 cargo xcode 

添加一些绑定文件

  • 在 Xcode 中打开 ios/Runner.xcodeproj, 接着把 $crate/$crate.xcodeproj 添加为子项目:File ---> Add Files to "Runner"

Flutter和Rust如何优雅的交互

  • 选择生成子项目,然后点击add

Flutter和Rust如何优雅的交互

  • 选中那个文件夹,就会生成在哪个文件下

Flutter和Rust如何优雅的交互

  • 点击 Runner 根项目,TARGETS ---> Build Phases ---> Target Dependencies :请添加 $crate-staticlib

Flutter和Rust如何优雅的交互

  • 展开 Link Binary With Libraries:添加 lib$crate_static.a

Flutter和Rust如何优雅的交互

  • 添加完毕后

Flutter和Rust如何优雅的交互

绑定头文件

flutter_rust_bridge_codegen 会创建一个 C 头文件,里面列出了 Rust 库导出的所有符号,需要使用它,确保 Xcode 不会将符号去除。

在项目中需要添加 ios/Runner/bridge_generated.h (或者 macos/Runner/bridge_generated.h)

  • 执行下述生成命令,会生成对应头文件,自动放到ios和macos目录下;可以封装成脚本,每次跑脚本就行了
flutter_rust_bridge_codegen -r rust/src/api.rs -d lib/ffi/rust_ffi/rust_ffi.dart -c ios/Runner/bridge_generated.h -c macos/Runner/bridge_generated.h 

Flutter和Rust如何优雅的交互

  • ios/Runner/Runner-Bridging-Header.h 中添加

    #import "GeneratedPluginRegistrant.h" +#import "bridge_generated.h"  
  • ios/Runner/AppDelegate.swift 中添加

     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)  } 

macOS

说明

macos上面指向有个很奇怪的情况,官方文档说明的是需要链接$crate-cdylib$crate.dylib ;但是链接这个库,用xcode编译可以执行,但是使用android studio直接编译执行的时候会报错

与iOS保持一致,链接 $crate-staticliblib$crate_static.a ,可以顺利执行

下面配置,大家按需配置,我这边使用静态库能成功,链接动态库会失败

开始配置

  • 需要先生成子项目,如果在配置iOS的时候已经执行了该命令,就不需要再次执行了(当然,再次执行也没问题)
# 在rust项目下执行该命令 cargo xcode 
  • 在 Xcode 中打开 macos/Runner.xcodeproj, 接着把 $crate/$crate.xcodeproj 添加为子项目:File ---> Add Files to "Runner"

Flutter和Rust如何优雅的交互

  • 点击 Runner 根项目,TARGETS ---> Build Phases ---> Target Dependencies :请添加 $crate-staticlib (或者 $crate-staticlib )

Flutter和Rust如何优雅的交互

  • 展开 Link Binary With Libraries: 添加 lib$crate_static.a (或者 $crate.dylib )

Flutter和Rust如何优雅的交互

  • 需要注意的是,如果使用了动态库,编译报找不到 $crate.dylib的时候
    • 可以在Link Binary With Libraries的时候,macOS添加的**.dylib要选择Optional
    • 这个问题可能并不是必现

Flutter和Rust如何优雅的交互

  • Flutter 在 MacOS 上默认不使用符号,我们需要添加我们自己的

    • Build Settings 标签页中

    • Objective-C Bridging Header 设置为: Runner/bridge_generated.h

Flutter和Rust如何优雅的交互

  • 还需要把bridge_generated.h文件加入macos项目

Flutter和Rust如何优雅的交互

Flutter和Rust如何优雅的交互

Flutter和Rust如何优雅的交互

  • macos/Runner/AppDelegate.swift 中添加

    import Cocoa import FlutterMacOS  @NSApplicationMain class AppDelegate: FlutterAppDelegate {   override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { +   dummy_method_to_enforce_bundling()     return true   } } 

Windows / Linux

说明

目前使用flutter_rust_bridge等库保持在1.51.0版本

  • 亲测在windows,android,ios,macos可以编译运行,linux(暂时没精力折腾)

flutter_rust_bridge更新到1.54.0版本

  • 该版本有个比较大的改动,生成代码改动也比较大,需要安装最新版flutter_rust_bridge_codegen去生成代码
  • 该版本在android,ios,macos可以编译运行,在windows上会报错
failed to run custom build command for `dart-sys v2.0.1` Microsoft.CppCommon.targets(247,5): error MSB8066 

猜测是作者新版本dart-sys v2.0.1这个库有问题,导致编译产物路径出了问题,报错了上面的错

目前本demo的版本号限制死在1.51.0版本;后面作者可能会解决该问题,需要使用新版本可自行尝试

  • 在windows上安装编译生成库,需要安装指定版本
# 指定版本 cargo install flutter_rust_bridge_codegen --version 1.51.0 --force 

重要说明

因为windows上编译需要下载Corrosion,需要开启全局xx上网,不然可能在编译的时候,会存在无法下载Corrosion的报错

推荐工具使用sstap 1.0.9.7版本,这个版本内置全局规则(可戴笠软件,不仅限浏览器),后面的版本该规则被删了

rust.make

git clone https://github.com/corrosion-rs/corrosion.git # Optionally, specify -DCMAKE_INSTALL_PREFIX=<target-install-path>. You can install Corrosion anyway cmake -Scorrosion -Bbuild -DCMAKE_BUILD_TYPE=Release cmake --build build --config Release # This next step may require sudo or admin privileges if you're installing to a system location, # which is the default. cmake --install build --config Release 

windows和linux需要添加个rust.make文件:

  • rust.make里面标注的内容需要和Cargo.toml里name保持一致

Flutter和Rust如何优雅的交互

  • rust.make
# We include Corrosion inline here, but ideally in a project with # many dependencies we would need to install Corrosion on the system. # See instructions on https://github.com/AndrewGaspar/corrosion#cmake-install # Once done, uncomment this line: # find_package(Corrosion REQUIRED)  include(FetchContent)  FetchContent_Declare(     Corrosion     GIT_REPOSITORY https://github.com/AndrewGaspar/corrosion.git     GIT_TAG origin/master # Optionally specify a version tag or branch here )  FetchContent_MakeAvailable(Corrosion)  corrosion_import_crate(MANIFEST_PATH ../rust/Cargo.toml CRATES rust_ffi)  # Flutter-specific  set(CRATE_NAME "rust_ffi")  target_link_libraries(${BINARY_NAME} PRIVATE ${CRATE_NAME})  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${CRATE_NAME}-shared>) 

调整

  • Windows:在windows/CMakeLists.txt添加rust.cmake文件
 # Generated plugin build rules, which manage building the plugins and adding  # them to the application.  include(flutter/generated_plugins.cmake)  +include(./rust.cmake)   # === Installation ===  # Support files are copied into place next to the executable, so that it can 
  • Linux:在 Linux 上,你需要将 CMake 的最低版本升到 3.12,这是 Corrosion 的要求,rust.cmake 依赖 Corrosion。需求修改 linux/CMakeLists.txt 的这一行
-cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.12)  ...  # Generated plugin build rules, which manage building the plugins and adding # them to the application. include(flutter/generated_plugins.cmake)  +include(./rust.cmake)  # === Installation === # By default, "installing" just makes a relocatable bundle in the build 

使用

  • 调用
class NativeFFI {   NativeFFI._();    static DynamicLibrary? _dyLib;    static DynamicLibrary get dyLib {     if (_dyLib != null) return _dyLib!;      const base = 'rust_ffi';     if (Platform.isIOS) {       _dyLib = DynamicLibrary.process();     } else if (Platform.isMacOS) {       _dyLib = DynamicLibrary.executable();     } else if (Platform.isAndroid) {       _dyLib = DynamicLibrary.open('lib$base.so');     } else if (Platform.isWindows) {       _dyLib = DynamicLibrary.open('$base.dll');     } else {       throw Exception('DynamicLibrary初始化失败');     }      return _dyLib!;   } }  class NativeFun {   static final _ffi = RustFfiImpl(NativeFFI.dyLib);    static Future<int> add(int left, int right) async {     int sum = await _ffi.add(left: left, right: right);     return sum;   } } 
  • 自动生成的类就不写了,就是上面使用的RustFfiImpl

Flutter和Rust如何优雅的交互

  • 使用
void main() {   runApp(const MyApp()); }  class MyApp extends StatelessWidget {   const MyApp({super.key});    @override   Widget build(BuildContext context) {     return const MaterialApp(title: 'Flutter Demo', home: MyHomePage());   } }  class MyHomePage extends StatefulWidget {   const MyHomePage({super.key});    @override   State<MyHomePage> createState() => _MyHomePageState(); }  class _MyHomePageState extends State<MyHomePage> {   int _counter = 0;    void _incrementCounter() async {     _counter = await NativeFun.add(_counter, 2);     setState(() {});   }    @override   Widget build(BuildContext context) {     return Scaffold(       appBar: AppBar(title: const Text('Rust_Bridge Demo')),       body: Center(         child: Text(           'Count:  $_counter',           style: Theme.of(context).textTheme.headline4,         ),       ),       floatingActionButton: FloatingActionButton(         onPressed: _incrementCounter,         tooltip: 'Increment',         child: const Icon(Icons.add),       ),     );   } } 
  • 效果

Flutter和Rust如何优雅的交互

结语

对于rust这块,这些配置确实有点麻烦,但是配置完,后面就不用管了

痛苦一次就行了.

发表评论

评论已关闭。

相关文章