如何通过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'); } }
关键说明
- 后台Isolate:创建独立的Isolate负责MIDI消息处理,确保回调触发时存在合法的Dart执行上下文。
- SendPort跨线程传递:将SendPort的native地址通过
dwInstance参数传递给MIDI回调,回调中通过该地址还原SendPort,将消息发送到Isolate中处理。 - 回调函数规范:保持
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
相关产品推荐
相关产品推荐

