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

Flutter通过FFI调用Win32的SetWinEventHook时消息循环致卡顿的解决方法

解决Dart中SetWinEventHook监控窗口事件失效/应用冻结问题

核心问题分析

你的问题根源在于线程不匹配和消息循环的错误实现:

  1. 当使用WINEVENT_OUTOFCONTEXT标志时,Windows系统会将事件消息派发到调用SetWinEventHook的线程的消息队列。你之前在主线程创建钩子,却在独立Isolate跑消息循环,系统无法将事件投递到正确的线程,导致收不到事件;如果主线程跑同步消息循环,又会阻塞UI/主线程导致冻结。
  2. NativeCallable.isolateLocal是绑定当前Isolate的,主线程创建的回调无法在Isolate线程正常执行,反之亦然。
  3. 异步版本的消息循环用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('监控结束,资源已释放');
}

关键修改说明

  1. 线程绑定:钩子创建、回调注册、消息循环全部在同一个Isolate中执行,确保系统能将事件消息投递到正确的线程队列。
  2. NativeCallable正确使用:在Isolate内部创建isolateLocal类型的回调,避免跨线程执行问题。
  3. 主线程不阻塞:所有阻塞的消息循环逻辑隔离在独立Isolate中,主线程保持响应。
  4. 资源清理:添加UnhookWinEvent、callbackPtr.close()和内存释放代码,避免内存泄漏。
  5. 事件范围调整:明确设置eventMin和eventMax覆盖所有目标事件,确保不会遗漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 18:24:52