#channel #dart #thread #background-thread #deserialize #zero-copy #platform

irondash_message_channel

Rust对irondash MessageChannel的接口

9个版本 (重大更新)

0.7.0 2024年5月10日
0.6.0 2024年2月8日
0.5.0 2023年12月26日
0.4.0-dev22023年11月29日
0.1.0 2022年11月15日

#174 in 解析器实现

Download history 1182/week @ 2024-04-27 1462/week @ 2024-05-04 1703/week @ 2024-05-11 2064/week @ 2024-05-18 1618/week @ 2024-05-25 1659/week @ 2024-06-01 1251/week @ 2024-06-08 1054/week @ 2024-06-15 1033/week @ 2024-06-22 840/week @ 2024-06-29 1080/week @ 2024-07-06 1067/week @ 2024-07-13 1268/week @ 2024-07-20 1209/week @ 2024-07-27 1356/week @ 2024-08-03 866/week @ 2024-08-10

每月4,895次下载

MIT许可证

225KB
5.5K SLoC

irondash_message_channel

Rust-dart桥梁,类似于Flutter的平台通道。

此包允许使用类似于Flutter平台通道的模式从Dart调用Rust代码,反之亦然。

  • 易于使用的便捷API(Dart端模仿平台通道API)。
  • 高性能
    • 从Rust调用Dart时,二进制数据无需复制
    • 从Dart调用Rust时,二进制数据正好复制一次
  • Rust宏,用于自动序列化和反序列化(类似于Serde,但针对零复制进行了优化)
  • 无需代码生成
  • 线程亲和力 - Rust通道对应物绑定到创建通道的线程。只要运行RunLoop,您就可以在平台线程或任何后台线程上拥有通道。
  • 终结处理程序 - 当Dart对象被垃圾回收时,Rust端可以收到通知。
  • 异步支持

使用方法

初始设置

因为Rust代码需要访问Dart FFI api,所以需要进行一些设置。

/// 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());

    // This function will be called by MessageChannel with opaque FFI
    // initialization data. From it you should call
    // `irondash_init_message_channel_context` and do any other initialization,
    // i.e. register rust method channel handlers.
    final function =
        dylib.lookup<NativeFunction<MessageChannelContextInitFunction>>(
            "my_example_init_message_channel_context");
    return MessageChannelContext.forInitFunction(function);
}

final nativeContext = _initNativeContext();

// Now you can create method channels

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

简单用法

设置完成后,您可以使用Dart NativeMethodChannel,类似于Flutter的 PlatformChannel


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 trait,或者如果您想使用async/await,则实现AsyncMethodHandler

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 {
    // This will be called right after method channel registration.
    // You can use invoker to call Dart methods handlers.
    fn assign_invoker(&self, invoker: AsyncMethodInvoker) {
        self.invoker.set(invoker);
    }

    // ...
}

请注意,要使用Invoker,您需要知道目标isolateId。您可以从Rust中处理方法调用时获取MethodCall结构中获取它。您还可以在isolate被销毁时收到通知

impl MethodHandler for MyHandler {
    /// Called when isolate is about to be destroyed.
    fn on_isolate_destroyed(&self, _isolate: IsolateId) {}
    // ...

要查看消息通道的实际应用,请查看示例项目

线程考虑因素

MethodHandlerAsyncMethodHandler 与创建它们的线程绑定。该线程必须运行一个 RunLoop。对于平台线程,这是隐含成立的。要在后台线程使用通道,您需要创建一个 RunLoop 并自行运行它。

MethodInvokerSend。它可以在线程之间传递,方法调用的响应将在发送请求的同一线程上接收。同样,该线程必须有一个正在运行的 RunLoop

值之间的转换

Value 表示可以在 Rust 和 Dart 之间发送的所有类型。为了简化 Rust 端的序列化和反序列化,irondash_message_channel 提供了 IntoValueTryFromValue 过程宏,它们为 Value 生成 TryInto<YourStruct>From<YourStruct> 特性。这是一个可选功能

[dependencies]
irondash_message_channel = { version = "0.6.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();

还支持更多高级映射选项,例如

#[derive(IntoValue, TryFromValue)]
#[irondash(tag = "t", content = "c")]
#[irondash(rename_all = "UPPERCASE")]
enum Enum3CustomTagContent {
    Abc,
    #[irondash(rename = "_Def")]
    Def,
    SingleValue(i64),
    #[irondash(rename = "_DoubleValue")]
    DoubleValue(f64, f64),
    Xyz {
        x: i64,
        s: String,
        z1: Option<i64>,
        #[irondash(skip_if_empty)]
        z2: Option<i64>,
        z3: Option<f64>,
    },
}

与 serde 不同,.into()try_into() 消耗原始值,这使得零拷贝序列化和反序列化成为可能。

依赖项

~1–15MB
~143K SLoC