Rust跨平台消息通道库irondash_message_channel_derive的使用,实现高效安全的进程间通信与数据序列化
Rust跨平台消息通道库irondash_message_channel_derive的使用,实现高效安全的进程间通信与数据序列化
简介
irondash_message_channel是一个类似Flutter平台通道的Rust-Dart桥接库。该包允许使用类似Flutter平台通道的模式从Dart调用Rust代码,反之亦然。
主要特点:
- 易于使用的便捷API(Dart端模仿平台通道API)
- 高性能
- 从Rust调用Dart时二进制数据零拷贝
- 从Dart调用Rust时二进制数据只复制一次
- 用于自动序列化和反序列化的Rust宏(类似Serde但优化为零拷贝)
- 无需代码生成
- 线程亲和性 - Rust通道对应部分绑定到创建通道的线程
- 终结处理程序 - Rust端可以在Dart对象被垃圾回收时得到通知
- 异步支持
初始设置
由于Rust代码需要访问Dart FFI API,需要进行一些设置。
Dart端:
/// initialize context for Native library.
MessageChannelContext _initNativeContext() {
final dylib = defaultTargetPlatform == TargetPlatform.android
? DynamicLibrary.open("libmyexample.so")
: (defaultTargetPlatform == TargetPlatform.windows
? DynamicLibrary.open("myexample.dll"
: DynamicLibrary.process());
final function =
dylib.lookup<NativeFunction<MessageChannelContextInitFunction>>(
"my_example_init_message_channel_context");
return MessageChannelContext.forInitFunction(function);
}
final nativeContext = _initNativeContext();
final _channel =
NativeMethodChannel('my_method_channel', context: nativeContext);
_channel.setMethodCallHandler(...);
Rust端:
use irondash_message_channel::*;
#[no_mangle]
pub extern "C" fn my_example_init_message_channel_context(data: *mut c_void) -> FunctionResult {
irondash_init_message_channel_context(data)
}
简单使用
设置完成后,可以像使用Flutter的PlatformChannel一样使用Dart的NativeMethodChannel:
Dart端:
final _channel = NativeMethodChannel('my_method_channel', context: nativeContext);
_channel.setMessageHandler((call) async {
if (call.method == 'myMethod') {
return 'myResult';
}
return null;
});
final res = await _channel.invokeMethod('someMethod', 'someArg');
Rust端可以实现MethodHandler特性用于非异步版本,或AsyncMethodHandler如果想使用async/await:
同步版本:
use irondash_message_channel::*;
struct MyHandler {}
impl MethodHandler for MyHandler {
fn on_method_call(&self, call: MethodCall, reply: MethodCallReply) {
match call.method.as_str() {
"getMeaningOfUniverse" => {
reply.send_ok(42);
}
_ => reply.send_error(
"invalid_method".into(),
Some(format!("Unknown Method: {}", call.method)),
Value::Null,
),
}
}
}
fn init() {
let handler = MyHandler {}.register("my_method_channel");
// make sure handler is not dropped, otherwise it can't handle method calls.
}
异步版本:
use irondash_message_channel::*;
struct MyHandler {}
#[async_trait(?Send)]
impl AsyncMethodHandler for MyHandler {
async fn on_method_call(&self, call: MethodCall) -> PlatformResult {
match call.method.as_str() {
"getMeaningOfUniverse" => {
Ok(42.into())
}
_ => Err(PlatformError {
code: "invalid_method".into(),
message: Some(format!("Unknown Method: {}", call.method)),
detail: Value::Null,
}),
}
}
}
fn init() {
let handler = MyHandler {}.register("my_method_channel");
// make sure handler is not dropped, otherwise it can't handle method calls.
}
从Rust调用Dart
use irondash_message_channel::*;
struct MyHandler {
invoker: Late<AsyncMethodInvoker>,
}
#[async_trait(?Send)]
impl AsyncMethodHandler for MyHandler {
fn assign_invoker(&self, invoker: AsyncMethodInvoker) {
self.invoker.set(invoker);
}
// ...
}
完整示例
下面是一个完整的跨平台通信示例,展示如何在Rust和Dart之间传递复杂数据结构:
首先在Cargo.toml中添加依赖:
[dependencies]
irondash_message_channel = { version = "0.1.0", features = ["derive"] }
async-trait = "0.1"
Rust端代码:
use async_trait::async_trait;
use irondash_message_channel::*;
// 定义可序列化的数据结构
#[derive(TryFromValue, IntoValue, Debug)]
struct User {
id: i64,
name: String,
email: String,
is_active: bool,
}
#[derive(IntoValue)]
struct UserList {
users: Vec<User>,
total: i32,
}
// 异步处理程序
struct UserHandler {
invoker: Late<AsyncMethodInvoker>,
}
#[async_trait(?Send)]
impl AsyncMethodHandler for UserHandler {
fn assign_invoker(&self, invoker: AsyncMethodInvoker) {
self.invoker.set(invoker);
}
async fn on_method_call(&self, call: MethodCall) -> PlatformResult {
match call.method.as_str() {
"getUserList" => {
// 模拟从数据库获取用户列表
let users = vec![
User {
id: 1,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
is_active: true,
},
User {
id: 2,
name: "Bob".to_string(),
email: "bob@example.com".to_string(),
is_active: false,
},
];
Ok(UserList {
users,
total: 2,
}.into())
}
"activateUser" => {
// 从Dart接收用户ID并激活用户
let user_id: i64 = call.args.try_into()?;
println!("Activating user {}", user_id);
Ok(true.into())
}
_ => Err(PlatformError {
code: "invalid_method".into(),
message: Some(format!("Unknown Method: {}", call.method)),
detail: Value::Null,
}),
}
}
}
// 初始化函数
#[no_mangle]
pub extern "C" fn my_example_init_message_channel_context(data: *mut c_void) -> FunctionResult {
irondash_init_message_channel_context(data)
}
pub fn init() {
// 注册处理程序
let handler = UserHandler {
invoker: Late::new(),
}.register("user_channel");
// 保持handler不被丢弃
std::mem::forget(handler);
}
Dart端代码:
import 'package:flutter/material.dart';
import 'package:irondash_message_channel/irondash_message_channel.dart';
void main() {
// 初始化原生上下文
final nativeContext = _initNativeContext();
final userChannel = NativeMethodChannel('user_channel', context: nativeContext);
runApp(MyApp(userChannel: userChannel));
}
class MyApp extends StatelessWidget {
final NativeMethodChannel userChannel;
const MyApp({required this.userChannel, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: FutureBuilder(
future: _getUsers(),
builder: (context, snapshot) {
if (snapshot.hasData) {
final users = snapshot.data as List;
return ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
final user = users[index];
return ListTile(
title: Text(user['name']),
subtitle: Text(user['email']),
trailing: IconButton(
icon: Icon(user['is_active'] ? Itons.check : Icons.close),
onPressed: () => _activateUser(user['id']),
),
);
},
);
} else if (snapshot.hasError) {
return Text('Error: ${snapshot.error}');
}
return CircularProgressIndicator();
},
),
),
),
);
}
Future<List<dynamic>> _getUsers() async {
try {
final result = await userChannel.invokeMethod('getUserList');
return result['users'] as List;
} catch (e) {
print('Error getting users: $e');
rethrow;
}
}
Future<void> _activateUser(int userId) async {
try {
await userChannel.invokeMethod('activateUser', userId);
print('User $userId activated');
} catch (e) {
print('Error activating user: $e');
}
}
}
MessageChannelContext _initNativeContext() {
final dylib = DynamicLibrary.process();
final function = dylib.lookup<NativeFunction<MessageChannelContextInitFunction>>(
"my_example_init_message_channel_context");
return MessageChannelContext.forInitFunction(function);
}
线程考虑
MethodHandler和AsyncMethodHandler绑定到创建它们的线程。该线程必须运行RunLoop。对于平台线程来说这是隐式正确的。要在后台线程上使用通道,需要创建RunLoop并自行运行。
MethodInvoker是Send的。它可以在线程之间传递,方法调用的响应将在发送请求的同一线程上接收。同样,该线程必须有一个正在运行的RunLoop。
值与Value之间的转换
Value表示可以在Rust和Dart之间发送的所有类型。为了简化Rust端的序列化和反序列化,irondash_message_channel提供了IntoValue和TryFromValue proc宏,为Value生成TryInto<YourStruct>和From<YourStruct>特性。这是一个可选功能:
Cargo.toml:
[dependencies]
irondash_message_channel = { version = "0.1.0", features = ["derive"] }
使用示例:
#[derive(TryFromValue, IntoValue)]
struct AdditionRequest {
a: f64,
b: f64,
}
#[derive(IntoValue)]
struct AdditionResponse {
result: f64,
request: AdditionRequest,
}
let value: Value = get_value_from_somewhere();
let request: AdditionRequest = value.try_into()?;
let response: Value = AdditionResponse {
result: request.a + request.b,
request,
}.into();
与serde不同,.into()和try_into()消耗原始值,使得零拷贝序列化和反序列化成为可能。
Rust跨平台消息通道库irondash_message_channel_derive使用指南
下面是基于提供内容的完整示例demo,展示如何实现一个完整的Flutter与Rust通信应用:
完整示例:Flutter与Rust通信应用
Rust部分 (lib.rs)
use irondash_message_channel_derive::message_channel;
use serde::{Serialize, Deserialize};
use tokio::time::{Duration, sleep};
// 1. 定义可序列化的数据结构
#[derive(Serialize, Deserialize, Debug)]
struct User {
id: u64,
name: String,
email: String,
}
// 2. 定义消息通道接口
#[message_channel]
trait AppApi {
// 同步方法 - 计算阶乘
fn factorial(n: u32) -> u64;
// 异步方法 - 模拟获取用户数据
async fn get_user(user_id: u64) -> Result<User, String>;
// 事件流 - 发送周期性事件
fn watch_counter(interval_ms: u64) -> StreamHandler<u64>;
}
// 3. 实现服务端逻辑
struct AppApiImpl;
impl AppApi for AppApiImpl {
fn factorial(&self, n: u32) -> u64 {
(1..=n).fold(1, |acc, x| acc * x as u64)
}
async fn get_user(&self, user_id: u64) -> Result<User, String> {
// 模拟异步操作
sleep(Duration::from_millis(500)).await;
if user_id == 0 {
return Err("Invalid user ID".to_string());
}
Ok(User {
id: user_id,
name: format!("User {}", user_id),
email: format!("user{}@example.com", user_id),
})
}
fn watch_counter(&self, interval_ms: u64) -> StreamHandler<u64> {
let (sender, handler) = StreamHandler::new();
tokio::spawn(async move {
let mut counter = 0;
loop {
sender.send(counter).await.unwrap();
counter += 1;
sleep(Duration::from_millis(interval_ms)).await;
}
});
handler
}
}
// 4. 注册服务
#[no_mangle]
pub extern "C" fn init_app_api() {
let _handler = AppApiImpl.register();
}
Flutter部分 (Dart)
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final api = AppApi();
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Rust-Flutter Demo')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
child: Text('计算阶乘 (5!)'),
onPressed: () async {
final result = await api.factorial(5);
print('阶乘结果: $result'); // 输出: 120
},
),
SizedBox(height: 20),
ElevatedButton(
child: Text('获取用户数据'),
onPressed: () async {
try {
final user = await api.getUser(1);
print('用户数据: ${user.name}, ${user.email}');
} catch (e) {
print('错误: $e');
}
},
),
SizedBox(height: 20),
ElevatedButton(
child: Text('启动计数器'),
onPressed: () {
final stream = api.watchCounter(1000);
stream.listen((count) {
print('计数器: $count');
});
},
),
],
),
),
),
);
}
}
构建说明
- 在Rust项目的
Cargo.toml
中添加依赖:
[dependencies]
irondash_message_channel_derive = "0.1"
irondash_message_channel = "0.1"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
-
确保Flutter项目中已正确配置Rust FFI插件
-
在Flutter项目的
pubspec.yaml
中添加irondash_message_channel依赖
这个完整示例展示了:
- 同步方法调用(阶乘计算)
- 异步操作(获取用户数据)
- 事件流(计数器)
- 复杂数据结构传递(User结构体)
- 错误处理
所有通信都是类型安全的,Rust和Dart两边的数据结构会自动进行序列化/反序列化。