0


Flutter和Rust如何优雅的交互

前言

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

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

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

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

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

Flutter和Rust的交互

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

本文是循序渐进式,比较全面的介绍了flutter的ffi使用,ffigen使用,最后才是

  1. rust交互

介绍;如果对ffi和ffigen不太关心,也可直接阅读

  1. rust交互

内容

FFI交互方式

配置

Android

  • 需要先配置ndk
  1. # macndk.dir=/Users/***/Develop/SDK/android_sdk/ndk/21.3.6528147
  2. # windowsndk.dir=F:\\SDK\\AndroidSDK\\ndk\\21.3.6528147

image-20220912112118631

  • 安装下CMake

image-20220912120921839

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

image-20220911224040763

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

可以发现

  1. 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)

  • 效果

Sep-12-2022 18-02-58

iOS

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

image-20220911224706172

  • 效果

Sep-12-2022 18-02-58

macOS

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

image-20220911225343151

  • 效果

Kapture 2022-09-12 at 18.11.00

Windows

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

image-20220911225730410

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

image-20220806185943415

  • 效果

windows

交互

  • 通用加载:NativeFFI.dynamicLibrary
  1. classNativeFFI{NativeFFI._();staticDynamicLibrary? _dyLib;staticDynamicLibraryget dynamicLibrary {if(_dyLib !=null)return _dyLib!;if(Platform.isMacOS ||Platform.isIOS){
  2. _dyLib =DynamicLibrary.process();}elseif(Platform.isAndroid){
  3. _dyLib =DynamicLibrary.open('libnative_fun.so');}elseif(Platform.isWindows){
  4. _dyLib =DynamicLibrary.open('libnative_fun.dll');}else{throwException('DynamicLibrary初始化失败');}return _dyLib!;}}

Flutter同步调用Native

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

windows

Native同步触发Flutter回调

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

Kapture 2022-09-12 at 22.35.23

异步消息通信

说明

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

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

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

目前来看

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

使用

  • dart
  1. 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<IntPtrFunction(Pointer<Void>),
  2. int Function(Pointer<Void>)>("InitDartApiDL");if(initializeApi(NativeApi.initializeApiDLData)!=0){throw"Failed to initialize Dart API";}
  3. _receivePort =ReceivePort();
  4. _subscription = _receivePort!.listen(_handleNativeMessage);final registerSendPort =NativeFFI.dynamicLibrary.lookupFunction<VoidFunction(Int64 sendPort),voidFunction(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');}voidffiAsyncMessage(int a){_ensureNativeInitialized();finalvoidFunction(int x) asyncMessage =NativeFFI.dynamicLibrary
  5. .lookup<NativeFunction<VoidFunction(Int32)>>("NativeAsyncMessage").asFunction();asyncMessage(a);}voiddispose(){// TODO _unregisterReceivePort(_receivePort.sendPort.nativePort);
  6. _subscription?.cancel();
  7. _receivePort?.close();}
  • native
  1. // 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`
  2. DART_EXPORT intptr_t InitDartApiDL(void* data){returnDart_InitializeApiDL(data);}
  3. Dart_Port send_port_;
  4. DART_EXPORT voidRegisterSendPort(Dart_Port send_port){
  5. send_port_ = send_port;}void*thread_func(void*args){printf("thread_func Running on (%p)\n",pthread_self());sleep(2/* seconds */);// doing something
  6. Dart_CObject dart_object;
  7. dart_object.type = Dart_CObject_kInt64;
  8. dart_object.value.as_int64 =reinterpret_cast<intptr_t>(args);Dart_PostCObject_DL(send_port_,&dart_object);pthread_exit(args);}
  9. DART_EXPORT voidNativeAsyncMessage(int32_t x){printf("NativeAsyncCallback Running on (%p)\n",pthread_self());
  10. pthread_t message_thread;pthread_create(&message_thread,NULL, thread_func,(void*)&x);}

image-20220912222256653

  • 效果

Kapture 2022-09-12 at 22.39.37

ffigen使用

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

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

配置

  • ubuntu/linux- 安装 libclangdev: sudo apt-get install libclang-dev
  • Windows- 安装 Visual Studio with C++ development support- 安装 LLVM: winget install -e --id LLVM.LLVM
  • MacOS- 安装 Xcode- 安装 LLVM: brew install llvm
  • 引入ffigen- pub:https://pub.dev/packages/ffigen
  1. dependencies:
  2. ffigen:^7.2.0
  3. ffigen:
  4. # 输出生成的文件路径
  5. output:'lib/src/ffigen/two_num_add.dart'
  6. # 输出的类名
  7. name:NativeLibrary
  8. headers:
  9. # 配置需要生成的文件
  10. entry-points:-'ios/Classes/native/ffigen/add.cpp'
  11. # 保证只转换two_num_add.cpp文件,不转换其包含的库文件,建议加上
  12. include-directives:-'ios/Classes/native/ffigen/add.cpp'

生成文件

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

image-20221030230708385

  • ffigen生成命令
  1. dart run ffigen
  • add.cpp - 使用命令生成对应dart文件的时候,方法名前不能加我们定义的DART_API,不然无法生成对应dart文件- 编译的时候必须要加上DART_API,不然无法编译该方法- 有点无语,有知道能统一处理cpp文件方法的,还请在评论区告知呀
  1. #include<stdint.h>#ifdefWIN32#defineDART_APIextern"C"__declspec(dllexport)#else#defineDART_APIextern"C"__attribute__((visibility("default")))__attribute__((used))#endif// DART_API int32_t twoNumAddGen(int32_t x, int32_t y){// return x + y;// }int32_ttwoNumAddGen(int32_t x,int32_t y){return x + y;}
  • 生成的dart文件
  1. // AUTO GENERATED FILE, DO NOT EDIT.//// Generated by `package:ffigen`.import'dart:ffi'as ffi;classNativeLibrary{/// Holds the symbol lookup function.finalffi.Pointer<T>Function<Textendsffi.NativeType>(String symbolName)
  2. _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<Textendsffi.NativeType>(String symbolName)
  3. lookup): _lookup = lookup;
  4. int twoNumAddGen(
  5. int x,
  6. int y,){return_twoNumAddGen(
  7. x,
  8. y,);}
  9. late final _twoNumAddGenPtr =
  10. _lookup<ffi.NativeFunction<ffi.Int32Function(ffi.Int32,ffi.Int32)>>('twoNumAddGen');
  11. late final _twoNumAddGen =
  12. _twoNumAddGenPtr.asFunction<int Function(int, int)>();}

image-20221030230047893

使用

  • 通用加载:NativeFFI.dynamicLibrary
  1. classNativeFFI{NativeFFI._();staticDynamicLibrary? _dyLib;staticDynamicLibraryget dynamicLibrary {if(_dyLib !=null)return _dyLib!;if(Platform.isMacOS ||Platform.isIOS){
  2. _dyLib =DynamicLibrary.process();}elseif(Platform.isAndroid){
  3. _dyLib =DynamicLibrary.open('libnative_fun.so');}elseif(Platform.isWindows){
  4. _dyLib =DynamicLibrary.open('libnative_fun.dll');}else{throwException('DynamicLibrary初始化失败');}return _dyLib!;}}
  • 使用
  1. NativeLibrary(NativeFFI.dynamicLibrary).twoNumAddGen(a, b);
  • 效果

windows

rust 交互

使用flutter_rust_bridge:flutter_rust_bridge

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

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

准备

创建Rust项目

image-20221127191621699

  • 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/
  1. [package]
  2. name ="rust_ffi"
  3. version ="0.1.0"
  4. edition ="2021"[lib]
  5. name ="rust_ffi"crate-type=["staticlib","cdylib"][build-dependencies]
  6. flutter_rust_bridge_codegen ="=1.51.0"[dependencies]
  7. flutter_rust_bridge ="=1.51.0"
  8. flutter_rust_bridge_macros ="=1.51.0"
  • 写rust代码需要注意下,不要在lib.rs中写代码,不然生成文件无法获取导包

image-20221127194036971

Flutter项目

  • flutter项目正常创建就行了

image-20221127191323745

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

命令

  • 需要先安装下代码生成工具
  1. # 必须cargoinstall flutter_rust_bridge_codegen
  2. # iOS和macOS 必须需要cargoinstall cargo-xcode
  • 安装LLVM- ubuntu/linux- 安装 libclangdev: sudo apt-get install libclang-dev- Windows- 安装 Visual Studio with C++ development support- 安装 LLVM: winget install -e --id LLVM.LLVM- MacOS- 安装 Xcode- 安装 LLVM: brew install llvm
  • 生成命令
  1. flutter_rust_bridge_codegen -r rust/src/api.rs -d lib/ffi/rust_ffi/rust_ffi.dart
  • 如果需要iOS和macOS,用下面的命令,说明请参照:配置 —> iOS / macOS
  1. 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升级对应版本- 升级完版本后需要重新跑下该命令
  1. # 自动安装最新版本cargoinstall flutter_rust_bridge_codegen
  2. # 指定版本cargoinstall flutter_rust_bridge_codegen --version1.51.0 --force

配置

Android

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

image-20220912112118631

  • 需要在gradle.properties配置下
  1. # macANDROID_NDK=/Users/***/Develop/SDK/android_sdk/ndk/21.3.6528147
  2. # windowsANDROID_NDK=F:\\SDK\\AndroidSDK\\ndk\\21.3.6528147

image-20221207221223355

  • android/app/build.gradle 的最后添加下面几行 - ANDROID_NDK 就是在上面配置的变量- “…/…/rust”:此处请配置自己rust项目文件夹命名
  1. [newTuple2('Debug',''),newTuple2('Profile','--release'),newTuple2('Release','--release')].each {def taskPostfix = it.first
  2. def profileMode = it.second
  3. tasks.whenTaskAdded { task ->if(task.name =="javaPreCompile$taskPostfix"){
  4. task.dependsOn "cargoBuild$taskPostfix"}}
  5. 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 \
  6. -t armeabi-v7a -t arm64-v8a -t x86_64 -t x86 \
  7. -o ../android/app/src/main/jniLibs build $profileMode"""
  8. workingDir "../../rust"
  9. environment "ANDROID_NDK_HOME","$ANDROID_NDK"if(org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.currentOperatingSystem.isWindows()){
  10. commandLine 'cmd','/C', ndk_command
  11. }else{
  12. commandLine 'sh','-c', ndk_command
  13. }}}

iOS

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

添加一些绑定文件

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

image-20221128213457207

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

image-20221128213836779

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

image-20221128214042835

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

image-20221128221947641

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

image-20221128222232489

  • 添加完毕后

image-20221128223107411

绑定头文件

  1. flutter_rust_bridge_codegen

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

在项目中需要添加

  1. ios/Runner/bridge_generated.h

(或者

  1. macos/Runner/bridge_generated.h

)

  • 执行下述生成命令,会生成对应头文件,自动放到ios和macos目录下;可以封装成脚本,每次跑脚本就行了
  1. 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

image-20221128224807766

  • 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上面指向有个很奇怪的情况,官方文档说明的是需要链接

  1. $crate-cdylib

  1. $crate.dylib

;但是链接这个库,用xcode编译可以执行,但是使用android studio直接编译执行的时候会报错

与iOS保持一致,链接

  1. $crate-staticlib

  1. lib$crate_static.a

,可以顺利执行

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

开始配置

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

image-20221128213457207

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

image-20221204215117599

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

image-20221204215304940

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

image-20221204183323089

  • Flutter 在 MacOS 上默认不使用符号,我们需要添加我们自己的- 在 Build Settings 标签页中- 把 Objective-C Bridging Header 设置为: Runner/bridge_generated.h

image-20221204220901933

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

image-20221204215640724

image-20221204215815410

image-20221204215951922

  • macos/Runner/AppDelegate.swift 中添加import Cocoaimport FlutterMacOS@NSApplicationMainclass AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {+ dummy_method_to_enforce_bundling() return true }}

Windows / Linux

说明

目前使用

  1. flutter_rust_bridge

等库保持在

  1. 1.51.0

版本

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

  1. flutter_rust_bridge

更新到

  1. 1.54.0

版本

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

猜测是作者新版本

  1. dart-sys v2.0.1

这个库有问题,导致编译产物路径出了问题,报错了上面的错

目前本demo的版本号限制死在

  1. 1.51.0

版本;后面作者可能会解决该问题,需要使用新版本可自行尝试

  • 在windows上安装编译生成库,需要安装指定版本
  1. # 指定版本cargoinstall flutter_rust_bridge_codegen --version1.51.0 --force

重要说明

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

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

rust.make

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

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

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

image-20221128220006292

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

调整

  • Windows:在windows/CMakeLists.txt添加rust.cmake文件
  1. # Generated plugin build rules, which manage building the plugins and adding
  2. # them to the application.
  3. include(flutter/generated_plugins.cmake)
  4. +include(./rust.cmake)
  5. # === Installation ===
  6. # 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 的这一行
  1. -cmake_minimum_required(VERSION 3.10)
  2. +cmake_minimum_required(VERSION 3.12)
  3. ...
  4. # Generated plugin build rules, which manage building the plugins and adding
  5. # them to the application.
  6. include(flutter/generated_plugins.cmake)
  7. +include(./rust.cmake)
  8. # === Installation ===
  9. # By default, "installing" just makes a relocatable bundle in the build

使用

  • 调用
  1. classNativeFFI{NativeFFI._();staticDynamicLibrary? _dyLib;staticDynamicLibraryget dyLib {if(_dyLib !=null)return _dyLib!;const base ='rust_ffi';if(Platform.isIOS){
  2. _dyLib =DynamicLibrary.process();}elseif(Platform.isMacOS){
  3. _dyLib =DynamicLibrary.executable();}elseif(Platform.isAndroid){
  4. _dyLib =DynamicLibrary.open('lib$base.so');}elseif(Platform.isWindows){
  5. _dyLib =DynamicLibrary.open('$base.dll');}else{throwException('DynamicLibrary初始化失败');}return _dyLib!;}}classNativeFun{staticfinal _ffi =RustFfiImpl(NativeFFI.dyLib);staticFuture<int>add(int left, int right)async{
  6. int sum =await _ffi.add(left: left, right: right);return sum;}}
  • 自动生成的类就不写了,就是上面使用的RustFfiImpl

image-20221204220434524

  • 使用
  1. voidmain(){runApp(constMyApp());}classMyAppextendsStatelessWidget{constMyApp({super.key});@overrideWidgetbuild(BuildContext context){returnconstMaterialApp(title:'Flutter Demo', home:MyHomePage());}}classMyHomePageextendsStatefulWidget{constMyHomePage({super.key});@overrideState<MyHomePage>createState()=>_MyHomePageState();}class _MyHomePageState extendsState<MyHomePage>{
  2. int _counter =0;void_incrementCounter()async{
  3. _counter =awaitNativeFun.add(_counter,2);setState((){});}@overrideWidgetbuild(BuildContext context){returnScaffold(
  4. appBar:AppBar(title:constText('Rust_Bridge Demo')),
  5. body:Center(
  6. child:Text('Count: $_counter',
  7. style:Theme.of(context).textTheme.headline4,),),
  8. floatingActionButton:FloatingActionButton(
  9. onPressed: _incrementCounter,
  10. tooltip:'Increment',
  11. child:constIcon(Icons.add),),);}}
  • 效果

rust_windows

结语

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

痛苦一次就行了.

标签: flutter android ios

本文转载自: https://blog.csdn.net/CNAD666/article/details/128283202
版权归原作者 小呆呆666 所有, 如有侵权,请联系我们删除。

“Flutter和Rust如何优雅的交互”的评论:

还没有评论