快速调整WinUI 3窗口大小时触发System.ExecutionEngineException异常
解决WinUI3窗口调整大小时PInvoke.CallWindowProc抛出ExecutionEngineException的问题
核心原因
WinUI3桌面应用的窗口在快速调整大小时,可能触发窗口句柄的重建或销毁操作,导致你通过SetWindowLongPtr子类化窗口时保存的旧窗口过程指针(_oldWndProc)失效。后续窗口消息触发CallWindowProc调用时,引用了已被回收或无效的内存地址,从而抛出System.ExecutionEngineException。另外,窗口过程委托若被GC回收,也会引发此类内存访问异常。
解决方案步骤
绑定窗口子类化到窗口句柄生命周期
监听窗口的HandleChanged事件,在句柄创建时设置子类化,句柄销毁时恢复原窗口过程,避免使用失效的句柄或窗口过程指针:private IntPtr _oldWndProc; private readonly PInvoke.WNDPROC _wndProcDelegate = WndProc; // 类级别委托,防止GC回收 public MainWindow() { InitializeComponent(); HandleChanged += OnWindowHandleChanged; } private void OnWindowHandleChanged(object sender, EventArgs e) { var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this); if (hwnd != IntPtr.Zero) { // 仅在句柄有效时设置子类化 _oldWndProc = PInvoke.SetWindowLongPtr(hwnd, PInvoke.GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(_wndProcDelegate)); } else { // 句柄失效时恢复原窗口过程 if (_oldWndProc != IntPtr.Zero) { PInvoke.SetWindowLongPtr(hwnd, PInvoke.GWL_WNDPROC, _oldWndProc); _oldWndProc = IntPtr.Zero; } } }安全调用CallWindowProc
在窗口过程回调中,先校验_oldWndProc的有效性,若无效则调用DefWindowProc兜底,避免访问无效内存:private static IntPtr WndProc(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam) { // 处理热键消息(F6切换ToggleButton) const int HOTKEY_ID = 1001; if (msg == PInvoke.WM_HOTKEY && wParam == (IntPtr)HOTKEY_ID) { // 通过DispatcherQueue回到UI线程操作控件 Application.Current.DispatcherQueue.TryEnqueue(() => { var mainWindow = (MainWindow)Application.Current.Windows[0]; mainWindow.MyToggleButton.IsChecked = !mainWindow.MyToggleButton.IsChecked; }); return IntPtr.Zero; } // 获取当前窗口的旧窗口过程指针 var mainWindow = (MainWindow)Application.Current.Windows[0]; if (mainWindow._oldWndProc != IntPtr.Zero) { return PInvoke.CallWindowProc(mainWindow._oldWndProc, hwnd, msg, wParam, lParam); } // 兜底调用默认窗口过程 return PInvoke.DefWindowProc(hwnd, msg, wParam, lParam); }关键注意事项
- 窗口过程委托必须声明为类级别的字段,不能是局部变量,否则会被.NET GC回收,导致
CallWindowProc调用无效的内存地址。 - 操作UI控件时必须通过
DispatcherQueue.TryEnqueue切换到UI线程,窗口过程回调运行在消息循环线程,直接操作UI会引发线程安全问题。 - 快速调整窗口时会频繁触发
WM_SIZE、WM_SIZING等消息,确保窗口过程中的处理逻辑尽可能轻量化,避免阻塞消息循环。
- 窗口过程委托必须声明为类级别的字段,不能是局部变量,否则会被.NET GC回收,导致
额外排查点
- 检查热键注册逻辑,确保窗口销毁时调用
UnregisterHotKey释放资源,避免残留的热键消息干扰后续窗口实例。 - 使用CsWin32的安全封装,尽量避免直接操作原始
IntPtr,必要时使用SafeHandle管理窗口句柄,减少悬垂指针风险。
- 检查热键注册逻辑,确保窗口销毁时调用
内容的提问来源于stack exchange,提问作者Ryan Luu
相关产品推荐
相关产品推荐

