针对常规的网络应用型APP,Flutter + Go 可以很好的兼顾性能与开发效率,而对于某些需要极致控制内存占用,需要与C库进行高效交互的程序,Flutter + Rust 则是另一个可选项。

Flutter + Rust 最直接的方式可能是使用 Flutter Favorite 的库 flutter_rust_bridge,基于代码模板以及提供多个工具简化整个对接流程。然而,对于一些只需要导出少量 rust 接口的功能,使用 flutter_rust_bridge 未免引入了过多复杂性,而直接使用 dart ffi 反而是一个相对精简的实现。

Rust <-> C

配置 Cargo.toml 输出 C 库

[package]
name = "xxx"
version = "0.1.0"
edition = "2024"

[lib]
name = "xxx"
crate-type = ["cdylib", "rlib"]

基于 cbindgen 导出 .h 头文件,创建 cbindgen.toml:

language = "C"
header = "/* Auto-generated by cbindgen */"
include_version = true
braces = "SameLine"
line_length = 100
tab_width = 4
documentation = true
documentation_style = "doxy"

[export]
include = ["xxx"]
exclude = []

[fn]
rename_args = "GeckoCase"
args = "auto"
sort_by = "Name"

[struct]
rename_fields = "ScreamingSnakeCase"
must_use = "/*must_use*/"
derive_const_casts = true
derive_mut_casts = true

[enum]
rename_variants = "ScreamingSnakeCase"
must_use = "/*must_use*/"

[const]
allow_static_const = true
allow_constexpr = false

[macro_expansion]
bitflags = true 

然后更新 build.rs 编译脚本

use std::env;
use std::path::PathBuf;

fn main() {
    let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
    let output_dir = PathBuf::from(&crate_dir);
    
    // Generate the header file
    cbindgen::Builder::new()
        .with_crate(crate_dir.clone())
        .with_language(cbindgen::Language::C)
        .generate()
        .expect("Unable to generate bindings")
        .write_to_file(output_dir.join("bindings.h"));
    
    // Rerun if any of these change
    println!("cargo:rerun-if-changed=src/dart");
    println!("cargo:rerun-if-changed=cbindgen.toml");
} 

需要导出的Rust函数添加 #[unsafe(no_mangle)]extern "C",譬如,需要与 dart 对接必须要实现以下函数,接收 api_data 然后调用 Dart_InitializeApiDL 初始化,另外,Rust 到 Dart 的异步通讯可通过 dart_port 的方式实现:

#[unsafe(no_mangle)]
pub extern "C" fn initialize_dart_api(api_data: *mut ::core::ffi::c_void, dart_port: i64, is_debug: bool) {
    println!("Initializing Dart API");
    unsafe {
        if Dart_InitializeApiDL(api_data) != 0 {
            panic!("failed to initialize Dart DL C API: version mismatch. must update include/ to match Dart SDK version");
        }
    }

    // Store the dart port globally
    if let Ok(mut port) = DART_PORT.lock() {
        *port = Some(dart_port);
    }

    // Try to initialize logger, but don't panic if it's already initialized
    let log_level = if is_debug { "debug" } else { "info" };
    let _ = env_logger::try_init_from_env(Env::default().default_filter_or(log_level));
    info!("Dart API initialized with port: {}", dart_port);
}

这样打包Rust动态库的同时就会输出对应的头文件,譬如以上函数的定义:

void initialize_dart_api(void *api_data, int64_t dart_port, bool is_debug);

dart <-> C

dart 根据 C 头文件与动态库,通过 ffi 的形式交互

dart run ffigen --config ffigen.yaml

其中 ffigen.yaml:

# Run with `dart run ffigen --config ffigen.yaml`.
name: XXXBindings
description: |
  Bindings for `bindings.h`.

  Regenerate bindings with `dart run ffigen --config ffigen.yaml`.
output: 'lib/generated/xxx_bindings_generated.dart'
headers:
  entry-points:
    - '../core/bindings.h'
  include-directives:
    - '../core/bindings.h'
compiler-opts:
  - '-I/usr/lib/clang/20/include/'
  - '-I/usr/lib/clang/19/include/'
preamble: |
  // ignore_for_file: always_specify_types
  // ignore_for_file: camel_case_types
  // ignore_for_file: non_constant_identifier_names
comments:
  style: any
  length: full

运行后输出 xxx_bindings_generated.dart 为 dart 下的绑定,如下:

  void initialize_dart_api(
    ffi.Pointer<ffi.Void> api_data,
    int dart_port,
    bool is_debug,
  ) {
    return _initialize_dart_api(api_data, dart_port, is_debug);
  }

  late final _initialize_dart_apiPtr =
      _lookup<
        ffi.NativeFunction<
          ffi.Void Function(ffi.Pointer<ffi.Void>, ffi.Int64, ffi.Bool)
        >
      >('initialize_dart_api');
  late final _initialize_dart_api = _initialize_dart_apiPtr
      .asFunction<void Function(ffi.Pointer<ffi.Void>, int, bool)>();

使用时需要增加动态库加载功能,并且最好是增加一个wapper类封装,类似 Rust 常用的 unsafe 机制。

动态库加载:

final dartffi.DynamicLibrary _dylib = () {
  if (Platform.isMacOS || Platform.isIOS) {
    return dartffi.DynamicLibrary.open('$_libName.framework/$_libName');
  }
  if (Platform.isAndroid || Platform.isLinux) {
    return dartffi.DynamicLibrary.open('lib$_libName.so');
  }
  if (Platform.isWindows) {
    return dartffi.DynamicLibrary.open('$_libName.dll');
  }
  throw UnsupportedError('Unknown platform: ${Platform.operatingSystem}');
}();

wapper类实例化时必须先调用 initialize_dart_api 函数,譬如一个简单的实现, 启动时传入 _receivePort.sendPort.nativePort,然后通过监听 _receivePort 将 Rust 的异步消息以Stream的方式广播出去。

  final XXXBindings _xxxBindings = XXXBindings(_dylib);

  StreamController<Message>? _messageController;

  static XXXService get instance {
    _instance ??= XXXService._internal();
    return _instance!;
  }

  XXXService._internal() {
    _receivePort = ReceivePort();
    _messageController = StreamController<Message>.broadcast();
    _engineStartedController!.add(false);
    // Initialize the Dart API in the native library
    _xxxBindings.initialize_dart_api(
      dartffi
          .NativeApi
          .initializeApiDLData, // API data - pass null from Dart side
      _receivePort.sendPort.nativePort, // Native port ID
      kDebugMode, // Debug mode flag
    );

    // Start listening to the receive port
    _startListening();
  }

  void _startListening() {
    _receivePort.listen(
      (rawMessage) {
        if (_disposed || _messageController == null) return;

        try {
          if (rawMessage is Uint8List) {
            final fbMessage = fb.Message(rawMessage);
            final message = Message.fromFlatBuffer(fbMessage);
            // logInfo('Received message');
            _messageController!.add(message);
          } else {
            logWarning(
              'Received unexpected message type: ${rawMessage.runtimeType}',
            );
          }
        } catch (e, stackTrace) {
          logWarning('Error processing message: $e');
          logWarning('Stack trace: $stackTrace');
          // Add error to stream instead of silently ignoring
          _messageController!.addError(e, stackTrace);
        }
      },
      onError: (error, stackTrace) {
        logWarning('ReceivePort error: $error');
        logWarning('Stack trace: $stackTrace');
        _messageController?.addError(error, stackTrace);
      },
      onDone: () {
        logInfo('ReceivePort closed');
        _messageController?.close();
      },
    );
  }

内存管理

需要注意的是 Rust 下面申请的内存只能 Rust 下释放,Dart 亦同理。

譬如,如果Rust以如下方式返回 CString:

CString::new(xxx_string)
            .map(|c_string| c_string.into_raw())
            .unwrap_or(std::ptr::null_mut())

则需要提供一个接口到 dart,使该字符串使用后能释放:

#[unsafe(no_mangle)]
pub extern "C" fn free_string(ptr: *mut c_char) {
    if !ptr.is_null() {
        unsafe {
            let _ = CString::from_raw(ptr);
        }
    }
}

对于 dart 申请的内存,也可以使用类似的机制 :

final dataPtr = malloc.allocate<dartffi.Uint8>(...)
try {
  // ...
} finally {
  malloc.free(dataPtr);
}