You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过Dart FFI向Win32函数传递回调?MIDI回调报错解决

Windows Flutter MIDI回调报错处理

错误含义

这个错误的核心原因是:Windows的MIDI回调由系统线程触发,而Dart FFI要求Native回调必须在某个Dart Isolate的上下文环境中执行。当MIDI设备按键触发回调时,执行回调的线程不属于任何Dart Isolate,因此Dart VM拒绝执行回调逻辑,抛出该错误。

解决方法:用Isolate+SendPort实现安全回调

通过创建独立的后台Isolate管理MIDI消息接收,利用SendPort跨线程传递数据,确保回调逻辑在合法的Isolate上下文内执行。

修改后的完整代码示例

import 'dart:isolate';
import 'dart:ffi';
import 'package:win32/win32.dart';

SendPort? _midiSendPort;

// 后台Isolate入口:专门处理MIDI消息
void _midiIsolateEntry(SendPort mainSendPort) {
  final receivePort = ReceivePort();
  mainSendPort.send(receivePort.sendPort);

  // 监听来自Native回调的MIDI消息
  receivePort.listen((message) {
    if (message is List<int>) {
      final wMsg = message[0];
      final dwParam1 = message[1];
      print('MIDI消息: $wMsg 参数1: $dwParam1');
      // 这里可以进一步处理消息,比如通知UI层
    }
  });
}

// 符合Win32规范的MIDI回调函数
static void midiInCallback(
    int hMidiIn,
    int wMsg,
    int dwInstance,
    int dwParam1,
    int dwParam2,
) {
  // 从dwInstance中取出SendPort指针,转换为可用的SendPort对象
  final sendPortPointer = Pointer<Void>.fromAddress(dwInstance);
  final sendPort = NativeApi.postCObject.cast<SendPort Function(Pointer<Void>)>()(sendPortPointer);
  
  // 将MIDI消息发送到后台Isolate处理
  sendPort?.send([wMsg, dwParam1]);
}

// 初始化MIDI设备
void initMidiDevice() async {
  // 创建后台Isolate,建立消息通道
  final mainReceivePort = ReceivePort();
  await Isolate.spawn(_midiIsolateEntry, mainReceivePort.sendPort);
  _midiSendPort = await mainReceivePort.first;

  final Pointer<HMIDIIN> hMidiDevice = malloc();

  // 将SendPort的native地址作为dwInstance传递给MIDI回调
  final sendPortAddress = _midiSendPort!.nativePort;

  // 注册回调函数指针
  Pointer<NativeFunction<MidiInProc>> callbackPointer =
      Pointer.fromFunction(midiInCallback);

  // 打开MIDI设备
  final result = midiInOpen(
    hMidiDevice,
    0, // 使用第一个MIDI输入设备
    callbackPointer.address,
    sendPortAddress,
    CALLBACK_FUNCTION,
  );

  if (result == MMSYSERR_NOERROR) {
    midiInStart(hMidiDevice.value);
    print('MIDI设备启动成功');
  } else {
    print('打开MIDI设备失败,错误码:$result');
  }
}

关键说明

  1. 后台Isolate:创建独立的Isolate负责MIDI消息处理,确保回调触发时存在合法的Dart执行上下文。
  2. SendPort跨线程传递:将SendPort的native地址通过dwInstance参数传递给MIDI回调,回调中通过该地址还原SendPort,将消息发送到Isolate中处理。
  3. 回调函数规范:保持midiInCallback为静态函数,参数类型严格匹配Win32的MidiInProc定义(HMIDIIN、UINT、DWORD、DWORD、DWORD对应Dart的int类型)。

备选方案:窗口回调(适用于桌面应用)

如果是Flutter桌面应用,也可以通过窗口消息接收MIDI事件:

  • 使用CALLBACK_WINDOW替代CALLBACK_FUNCTION,传递Flutter窗口的HWND给midiInOpen
  • 子类化窗口,处理MM_MIM_DATA等MIDI消息
    这种方式需要额外处理窗口消息转发,复杂度略高,适合熟悉Win32窗口编程的场景。

内容的提问来源于stack exchange,提问作者user4401

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 15:06:18