前言

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

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

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

目前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

  • 安装下CMake

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

  • 因为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)

  • 效果

iOS

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

  • 效果

macOS

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

  • 效果

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)

  • 这边可以将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的特有代码是必须要写的

  • 效果

交互

  • 通用加载: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;
}
  • 效果

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;
}
  • 效果

异步消息通信

说明

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

异步通信需要导入额外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);
}

  • 效果

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文件

  • 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)>();
}

使用

  • 通用加载: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);
  • 效果

rust 交互

使用flutter_rust_bridge:flutter_rust_bridge

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

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

准备

创建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项目

  • flutter项目正常创建就行了

  • 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下载请参考下图

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

  • 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"

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

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

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

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

  • 添加完毕后

绑定头文件

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

  • 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"

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

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

  • 需要注意的是,如果使用了动态库,编译报找不到 $crate.dylib的时候

    • 可以在Link Binary With Libraries的时候,macOS添加的**.dylib要选择Optional
    • 这个问题可能并不是必现

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

    • Build Settings 标签页中

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

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

  • 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保持一致

  • 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

  • 使用
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),
),
);
}
}
  • 效果

结语

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

痛苦一次就行了.

Flutter和Rust如何优雅的交互的更多相关文章

  1. Flutter 1.7 正式版发布

    今天,我们非常高兴地向大家宣布又一个正式版本的发布 -- Flutter 1.7,这是继上次 I/O 时众多重要功能发布以来的一次小更新.Flutter 1.7 包含了对 AndroidX 的支持,满 ...

  2. 《About Face 3:交互设计精髓》【PDF】下载

    <About Face 3:交互设计精髓>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230384328 内容简介 全书分成3篇:第1 ...

  3. Flutter 实现原理及在马蜂窝的跨平台开发实践

    一直以来,跨平台开发都是困扰移动客户端开发的难题. 在马蜂窝旅游 App 很多业务场景里,我们尝试过一些主流的跨平台开发解决方案, 比如 WebView 和 React Native,来提升开发效率和 ...

  4. Flutter 即学即用系列博客——09 EventChannel 实现原生与 Flutter 通信(一)

    前言 紧接着上一篇,这一篇我们讲一下原生怎么给 Flutter 发信号,即原生-> Flutter 还是通过 Flutter 官网的 Example 来讲解. 案例 接着上一次,这一次我们让原生 ...

  5. Flutter 即学即用系列博客——08 MethodChannel 实现 Flutter 与原生通信

    背景 前面我们讲了很多 Flutter 相关的知识点,但是我们并没有介绍怎样实现 Flutter 与原生的通信. 比如我在 Flutter UI 上面点击了一个按钮,我希望原生做一些处理,那么原生怎么 ...

  6. iOS开发者学习Flutter

    Flutter for iOS 开发者 本文档适用那些希望将现有 iOS 经验应用于 Flutter 的开发者.如果你拥有 iOS 开发基础,那么你可以使用这篇文档开始学习 Flutter 的开发. ...

  7. Flutter实战:手把手教你写Flutter Plugin

    前言 如果你对移动端有所关注,那么你一定会听说过Flutter.得益于Google,Flutter一经推出便得受到了广泛关注.很多开发者跃跃欲试,国内部分大厂,诸如美团.闲鱼等团队已经开始了Flutt ...

  8. Flutter Weekly Issue 52

    教程 一个易迁移.兼容性高的 Flutter 富文本方案 复杂业务如何保证Flutter的高性能高流畅度? 插件 flutter_color_models A wrapper for the Dart ...

  9. Flutter源码剖析(一):源码获取与构建

    概述 本文介绍了Flutter源码的获取与构建,后面会另有文章介绍Flutter源码的版本管理.开发环境搭建等主题. 准备工作 Flutter源码分为两个部分: flutter/flutter是框架层 ...

  10. 快成物流科技 x mPaaS | 小程序容器加持下的技术架构“提质增效”

      导言 从 2017 年开始,GMTC"移动技术大会"就更名为"大前端技术大会".发展至今,混合开发.原生开发.前端开发等概念正在深度融合,组成"大 ...

随机推荐

  1. 使用port-forward访问集群中的应用程序,以Redis 为例

    为Redis创建Deployment和Service 创建 Redis Deployment,YAML文件如下: apiVersion: apps/v1 kind: Deployment metada ...

  2. 2.Prometheus邮件报警配置

    1.安装配置 Alertmanager wget https://github.com/prometheus/alertmanager/releases/download/v0.20.0/alertm ...

  3. Docker方式安装Jenkins并且插件更改国内源

    参考网站:https://www.jenkins.io/zh/doc/book/installing/#在docker中下载并运行jenkins 建议使用的Docker映像是jenkinsci/blu ...

  4. 复现CVE-2022-10270(向日葵远程代码执行漏洞)

    警告 请勿使用本文提到的内容违反法律.本文不提供任何担保. 漏洞描述 向日葵是一款免费的,集远程控制电脑手机.远程桌面连接.远程开机.远程管理.支持内网穿透的一体化远程控制管理工具软件.CNVD披露了 ...

  5. PAT (Basic Level) Practice 1023 组个最小数 分数 20

    给定数字 0-9 各若干个.你可以以任意顺序排列这些数字,但必须全部使用.目标是使得最后得到的数尽可能小(注意 0 不能做首位).例如:给定两个 0,两个 1,三个 5,一个 8,我们得到的最小的数就 ...

  6. 关于将Azure云上磁盘导出-使用VirtualBox转换成vmdk格式的方法记录

    在工作中,经常会遇到虚拟磁盘文件格式的转换需求,尤其是在虚拟化迁移及云环境迁移到DC的虚拟化环境中 或者中转处理,如最近笔者遇到一个需要将Azure Cloud上的磁盘导出到VMware中,但Azur ...

  7. JPA入门学习集合springboot(一)

    1.在pom.xml文件中添加相应依赖 SpringData jpa和数据库MySql <!-- Spring Data JPA 依赖(重要) --> <dependency> ...

  8. 动态搜索图书:可以按书名、作者、出版社以及价格范围进行搜索。(在IDEA中mybatis)

    中午找了好久.好多人写的都驴头不对马嘴.自己实现后.才发现是真的不麻烦.也不知道人家咋想的.写的死麻烦还没用.老是搜出sql语句写死的.我要的是动态滴.自己写出后.总结了一下 1.按照书名.作者.出版 ...

  9. Redis5种数据类型

    字符串 @GetMapping("/string") public String stringTest(){ redisTemplate.opsForValue().set(&qu ...

  10. docker swarm快速部署redis分布式集群

    环境准备 四台虚拟机 192.168.2.38(管理节点) 192.168.2.81(工作节点) 192.168.2.100(工作节点) 192.168.2.102(工作节点) 时间同步 每台机器都执 ...