WPF:在Window.Closing事件中抛出未处理的异常导致e.Cancel失效

在WPF中,一种很常规的阻止窗口关闭按钮的方式,就是注册Window.Closing事件,然后在事件处理函数中设置e.Cancel=true。但是,在实践中,我遇到了这样设置不生效的情况,经过排查发现是由于Closing的事件处理函数中抛出了异常。虽然这个未处理的异常最后被全局异常处理函数捕获到了,没有导致进程的退出,但是它却使得Closing中的e.Cancel=true没有生效,导致我的窗口被关闭了。

下面,让我们看看,在Closing的处理函数中抛出异常,是如何导致窗口被关闭的。

  protected virtual void OnClosing(CancelEventArgs e)
  {
    this.VerifyContextAndObjectState();
    CancelEventHandler cancelEventHandler = (CancelEventHandler) this.Events[Window.EVENT_CLOSING];
    if (cancelEventHandler == null)
      return;
    cancelEventHandler((object) this, e);
  }

  private bool WmClose()
  {
    if (this.IsSourceWindowNull || this.IsCompositionTargetInvalid)
      return false;
    this._isClosing = true;
    CancelEventArgs e = new CancelEventArgs(false);
    try
    {
      this.OnClosing(e);
    }
    catch
    {
      this.CloseWindowFromWmClose();
      throw;
    }
    if (this.ShouldCloseWindow(e.Cancel))
    {
      this.CloseWindowFromWmClose();
      return false;
    }
    this._isClosing = false;
    this._dialogResult = new bool?();
    return true;
  }

上面是Window类中触发OnClosing事件的代码。OnClosing函数有两个地方调用,一处是Window类的公共Close方法,另一处就是这个WmClose函数,这个函数是窗口消息过程接收到WM_CLOSE的消息时调用的。

可以看到,WmClose在一个try catch块中调用了OnClosing,OnClosing调用了Closing事件的注册列表。当OnClosing抛出异常时,调用了CloseWindowFromWmClose函数。那么,是CloseWindowFromWmClose函数关闭了窗口吗?并不是,虽然CloseWindowFromWmClose函数释放了一些WPF窗口的内部资源,但没有关闭窗口。

继续查看WmClose函数可以看到,在进入函数时_isClosing字段被设置为true,退出函数时,这个字段被设置为false。如果在OnClosing中抛出了异常,_isClosing字段就不会被设置为false。那么,是这个字段导致了窗口被关闭吗?也不是,虽然这个字段能够标识窗口是否正在关闭中,但并不是窗口被关闭的直接原因。

其实,起关键作用的是WmClose函数的返回值。可以看到,它返回了一个布尔值,取下关闭窗口会返回true,不取消关闭则返回false,而如果抛出异常,则不会有返回值,而是直接抛出了异常。但是,在抛出异常前,调用了CloseWindowFromWmClose函数,就像返回false之前一样,可以猜测,抛出异常最后也会走到窗口关闭的逻辑,所以这里也需要释放掉WPF窗口的内部资源。

调用WmClose函数的是Window类里的WindowFilterMessage方法,这个方法有一个ref参数handled,WmClose的函数赋值给handled。

[SecurityCritical]
private IntPtr WindowFilterMessage(
  IntPtr hwnd,
  int msg,
  IntPtr wParam,
  IntPtr lParam,
  ref bool handled)
{
  //......
  if (this._swh != null && this._swh.CompositionTarget != null)
  {
    if (windowMessage == Window.WM_TASKBARBUTTONCREATED || windowMessage == Window.WM_APPLYTASKBARITEMINFO)
    {
      //......
    }
    else
    {
      switch (windowMessage)
      {
        case WindowMessage.WM_DESTROY:
          handled = this.WmDestroy();
          break;
        //......
        case WindowMessage.WM_CLOSE:
          handled = this.WmClose();
          break;
        //......
      }
    }
  }
  return zero;
}

WPF窗口关闭的逻辑是,接收到WM_CLOSE,触发Closing事件后,WM_CLOSE被继续传递给其他窗口过程函数来执行其他关闭窗口的代码,待窗口关闭后,会收到WM_DESTROY,触发Closed事件。在Closing事件的处理函数中,可以取消关闭,这时WmClose会返回true,这表示WM_CLOSE事件已经被处理掉了,不会再传递其他窗口过程函数,关闭窗口的代码也就不会执行。

但是这里并没有try-catch块,也就是说,WmClose中抛出的异常,会继续在调用堆栈中上行,直到某个地方被捕获掉,并且这个地方是使用参数handled的地方。

继续顺着调用堆栈找的话,可以找到HwndSubClass类的SubclassWndProc方法。

[SecurityCritical]
internal IntPtr SubclassWndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
{
  IntPtr num1 = IntPtr.Zero;
  bool flag = false;
  WindowMessage msg1 = (WindowMessage) msg;
  if (this._bond == HwndSubclass.Bond.Unattached)
    this.HookWindowProc(hwnd, new NativeMethods.WndProc(this.SubclassWndProc), Marshal.GetFunctionPointerForDelegate((Delegate) HwndSubclass.DefWndProcStub));
  else if (this._bond == HwndSubclass.Bond.Detached)
    throw new InvalidOperationException();
  IntPtr oldWndProc = this._oldWndProc;
  if (msg1 == HwndSubclass.DetachMessage)
  {
    if (wParam == IntPtr.Zero || wParam == (IntPtr) this._gcHandle)
    {
      int num2 = (int) lParam;
      num1 = this.CriticalDetach(num2 > 0) ? new IntPtr(1) : IntPtr.Zero;
      flag = num2 < 2;
    }
  }
  else
  {
    Dispatcher dispatcher = Dispatcher.FromThread(Thread.CurrentThread);
    if (dispatcher != null && !dispatcher.HasShutdownFinished)
    {
      if (this._dispatcherOperationCallback == null)
        this._dispatcherOperationCallback = new DispatcherOperationCallback(this.DispatcherCallbackOperation);
      if (HwndSubclass._paramDispatcherCallbackOperation == null)
        HwndSubclass._paramDispatcherCallbackOperation = new HwndSubclass.DispatcherOperationCallbackParameter();
      HwndSubclass.DispatcherOperationCallbackParameter callbackOperation = HwndSubclass._paramDispatcherCallbackOperation;
      HwndSubclass._paramDispatcherCallbackOperation = (HwndSubclass.DispatcherOperationCallbackParameter) null;
      callbackOperation.hwnd = hwnd;
      callbackOperation.msg = msg;
      callbackOperation.wParam = wParam;
      callbackOperation.lParam = lParam;
      if (dispatcher.Invoke(DispatcherPriority.Send, (Delegate) this._dispatcherOperationCallback, (object) callbackOperation) != null)
      {
        flag = callbackOperation.handled;
        num1 = callbackOperation.retVal;
      }
      HwndSubclass._paramDispatcherCallbackOperation = callbackOperation;
    }
    if (msg1 == WindowMessage.WM_NCDESTROY)
    {
      this.CriticalDetach(true);
      flag = false;
    }
  }
  if (!flag)
    num1 = this.CallOldWindowProc(oldWndProc, hwnd, msg1, wParam, lParam);
  return num1;
}

WindowFilterMessage方法被封装在_dispatcherOperationCallback中,callbackOperation封装了WindowFilterMessage方法的参数。flag = callbackOperation.handled这一行,相当于把WindowFilterMessage方法的handle参数赋值给flag了。结尾的CallOldWindowProc就是在调用其他的窗口过程函数。当flag为true时,代表着这个窗口消息已经被WindowFilterMessage处理掉了,就不会再调用CallOldWindowProc。

讲到这里,Closing如何取消窗口的关闭大概已经讲清楚了。但是还是没弄清楚Closing中抛出异常时,取消关闭是如何失效的,因为这里还是没有try-catch块。

但是,现在可以明确的是dispatcher.Invoke中捕获了异常,那么flag会是默认值false,窗口会继续执行关闭的逻辑。继续顺着上面代码中的dispatcher.Invoke深入,找到Dispatcher类的WrappedInvoke,callback是上面的_dispatcherOperationCallback,这里调用了ExceptionWrapper类的TryCatchWhen方法。

  [FriendAccessAllowed]
  internal object WrappedInvoke(
    Delegate callback,
    object args,
    int numArgs,
    Delegate catchHandler)
  {
    return Dispatcher._exceptionWrapper.TryCatchWhen((object) this, callback, args, numArgs, catchHandler);
  }
  public object TryCatchWhen(
    object source,
    Delegate callback,
    object args,
    int numArgs,
    Delegate catchHandler)
  {
    object obj = (object) null;
    try
    {
      obj = this.InternalRealCall(callback, args, numArgs);
    }
    catch (Exception ex) when (this.FilterException(source, ex))
    {
      if (!this.CatchException(source, ex, catchHandler))
        throw;
    }
    return obj;
  }

在TryCatchWhen方法中,终于看到了try-catch块,在catch代码中,如果CatchException方法返回true,那么这个异常就会被捕获掉。

  private bool CatchException(object source, Exception e, Delegate catchHandler)
  {
    //......
    return this.Catch != null && this.Catch(source, e);
  }

CatchException方法返回的实际上是this.Catch的返回值。this是啥?这里的this实际上Dispatcher._exceptionWrapper这个静态字段所指向的实例。那么,这个实例的Catch是啥?在Dispatcher类的静态构造函数中,对Dispatcher._exceptionWrapper.Catch赋值了,它实际上封装了Dispatcher类中的静态方法CatchExceptionStatic

    Dispatcher._exceptionWrapper.Catch += new ExceptionWrapper.CatchHandler(Dispatcher.CatchExceptionStatic);
  private static bool CatchExceptionStatic(object source, Exception e)
  {
    return ((Dispatcher) source).CatchException(e);
  }

  private bool CatchException(Exception e)
  {
    bool flag1 = false;
    if (this.UnhandledException != null)
    {
      this._unhandledExceptionEventArgs.Initialize(e, false);
      bool flag2 = false;
      try
      {
        this.UnhandledException((object) this, this._unhandledExceptionEventArgs);
        flag1 = this._unhandledExceptionEventArgs.Handled;
        flag2 = true;
      }
      finally
      {
        if (!flag2)
          flag1 = false;
      }
    }
    return flag1;
  }

CatchExceptionStatic方法中调用的UnhandledException,就是我们注册的全局异常事件,在事件处理函数中Handle掉事件,异常就不会继续抛出了。

Dispatcher.CurrentDispatcher.UnhandledException += App_DispatcherUnhandledException;

private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
    e.Handled = true;
}

总结一下的话,就是Closing中抛出的异常被全局异常事件处理掉了,异常处理完之后,WM_CLOSE的handled默认是false,就继续执行了窗口关闭的逻辑。

posted @ 2025-09-10 18:02  昏睡红猹  阅读(18)  评论(1)    收藏  举报