Flutter通过FFI调用Win32的SetWinEventHook时消息循环致卡顿的解决方法
解决Dart中SetWinEventHook监控窗口事件失效/应用冻结问题
核心问题分析
你的问题根源在于线程不匹配和消息循环的错误实现:
- 当使用
WINEVENT_OUTOFCONTEXT标志时,Windows系统会将事件消息派发到调用SetWinEventHook的线程的消息队列。你之前在主线程创建钩子,却在独立Isolate跑消息循环,系统无法将事件投递到正确的线程,导致收不到事件;如果主线程跑同步消息循环,又会阻塞UI/主线程导致冻结。 NativeCallable.isolateLocal是绑定当前Isolate的,主线程创建的回调无法在Isolate线程正常执行,反之亦然。- 异步版本的消息循环用
Future.delayed包裹GetMessage完全不符合Windows消息机制的要求——GetMessage是阻塞式调用,异步包裹会导致消息处理不及时,甚至丢失事件。
解决方案:钩子与消息循环同线程
将钩子创建、回调注册、消息循环全部放到同一个独立Isolate中,确保线程匹配,同时不阻塞主线程。
修改后的完整代码
import 'dart:ffi'; import 'dart:isolate'; import 'package:ffi/ffi.dart'; import 'package:win32/win32.dart'; // 定义监控所需的事件常量 const EVENT_SYSTEM_FOREGROUND = 3; const EVENT_OBJECT_HIDE = 0x8000; const EVENT_OBJECT_SHOW = 0x8004; const EVENT_SYSTEM_MINIMIZESTART = 0x0016; const WINEVENT_OUTOFCONTEXT = 0x0000; const WINEVENT_SKIPOWNTHREAD = 0x0001; const WINEVENT_SKIPOWNPROCESS = 0x0002; // API函数签名定义 typedef SetWinEventHookC = IntPtr Function( Uint32 eventMin, Uint32 eventMax, IntPtr hmodWinEventProc, Pointer<NativeFunction<WinEventProc>> lpfnWinEventProc, Uint32 idProcess, Uint32 idThread, Uint32 dwFlags, ); typedef SetWinEventHookDart = int Function( int eventMin, int eventMax, int hmodWinEventProc, Pointer<NativeFunction<WinEventProc>> lpfnWinEventProc, int idProcess, int idThread, int dwFlags, ); typedef WinEventProc = Void Function( IntPtr hWinEventHook, Uint32 eventType, IntPtr hwnd, Int32 idObject, Int32 idChild, Uint32 dwEventThread, Uint32 dwmsEventTime, ); typedef WinEventProcDart = void Function( int hWinEventHook, int eventType, int hwnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime, ); // 加载user32.dll并获取SetWinEventHook函数 final user32 = DynamicLibrary.open('user32.dll'); final SetWinEventHookDart setWinEventHook = user32.lookupFunction<SetWinEventHookC, SetWinEventHookDart>('SetWinEventHook'); void main() { // 启动独立的监控线程 startWindowMonitor(); // 主线程可正常执行其他逻辑,不会冻结 print('主线程正常运行中...'); } void startWindowMonitor() async { final receivePort = ReceivePort(); await Isolate.spawn(_monitorLoop, receivePort.sendPort); // 接收监控线程的消息 receivePort.listen((message) { if (message is String) { print(message); } }); } void _monitorLoop(SendPort sendPort) { // 在Isolate内部创建回调,绑定当前线程 final callbackPtr = NativeCallable<WinEventProc>.isolateLocal((hHook, eventType, hwnd, idObj, idChild, threadId, time) { switch (eventType) { case EVENT_SYSTEM_FOREGROUND: sendPort.send('窗口被激活'); break; case EVENT_OBJECT_HIDE: sendPort.send('窗口被最小化'); break; case EVENT_OBJECT_SHOW: sendPort.send('窗口从最小化恢复'); break; case EVENT_SYSTEM_MINIMIZESTART: sendPort.send('窗口开始最小化'); break; default: sendPort.send('触发其他事件: $eventType'); } }); // 创建钩子,确保事件范围覆盖所有需要监控的类型 final hook = setWinEventHook( EVENT_SYSTEM_MINIMIZESTART, // 监控的最小事件ID EVENT_OBJECT_SHOW, // 监控的最大事件ID 0, callbackPtr.nativeFunction, 0, // 监控所有进程 0, // 监控所有线程 WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNTHREAD | WINEVENT_SKIPOWNPROCESS, ); if (hook == NULL) { sendPort.send('钩子创建失败,错误码: ${GetLastError()}'); callbackPtr.close(); return; } sendPort.send('钩子创建成功,开始监控窗口事件'); // 运行标准Windows消息循环 final msg = calloc<MSG>(); while (GetMessage(msg, NULL, 0, 0) != 0) { DispatchMessage(msg); } // 资源清理,避免内存泄漏 UnhookWinEvent(hook); callbackPtr.close(); calloc.free(msg); sendPort.send('监控结束,资源已释放'); }
关键修改说明
- 线程绑定:钩子创建、回调注册、消息循环全部在同一个Isolate中执行,确保系统能将事件消息投递到正确的线程队列。
- NativeCallable正确使用:在Isolate内部创建
isolateLocal类型的回调,避免跨线程执行问题。 - 主线程不阻塞:所有阻塞的消息循环逻辑隔离在独立Isolate中,主线程保持响应。
- 资源清理:添加
UnhookWinEvent、callbackPtr.close()和内存释放代码,避免内存泄漏。 - 事件范围调整:明确设置
eventMin和eventMax覆盖所有目标事件,确保不会遗漏。
内容的提问来源于stack exchange,提问作者Inori
相关产品推荐
相关产品推荐

