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()消耗原始值,使得零拷贝序列化和反序列化成为可能。


1 回复

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');
                  });
                },
              ),
            ],
          ),
        ),
      ),
    );
  }
}

构建说明

  1. 在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"] }
  1. 确保Flutter项目中已正确配置Rust FFI插件

  2. 在Flutter项目的pubspec.yaml中添加irondash_message_channel依赖

这个完整示例展示了:

  • 同步方法调用(阶乘计算)
  • 异步操作(获取用户数据)
  • 事件流(计数器)
  • 复杂数据结构传递(User结构体)
  • 错误处理

所有通信都是类型安全的,Rust和Dart两边的数据结构会自动进行序列化/反序列化。

回到顶部