• 一个关于.Net的SaveFileDialog控件(Winform)的有趣问题


    场景:winform的程序中,有一个画面上放了一个Button,点击这个Button会调用.Net控件SaveFileDialog的ShowDialog方法。

    场景很简单,但是碰到了这样一个有趣的问题:

    在机器很慢的情况下,连续快速两次点击上述Button,会导致栈溢出异常(StackOverflowException)。

    由于机器很慢的情况难以模拟且不能稳定重现,所以做了一个简单的Demo,尝试在Button的点击事件中先用异步委托调一次SaveFileDialog.ShowDialog,然后再正常调用一次SaveFileDialog.ShowDialog,然后。。。问题重现了!

    SaveFileDialog的基类CommonDialog代码如下:

     1 public DialogResult ShowDialog(IWin32Window owner)
     2 {
     3     IntSecurity.SafeSubWindows.Demand();
     4     if (!SystemInformation.UserInteractive)
     5     {
     6         throw new InvalidOperationException(SR.GetString("CantShowModalOnNonInteractive"));
     7     }
     8     NativeWindow window = null;
     9     IntPtr zero = IntPtr.Zero;
    10     DialogResult cancel = DialogResult.Cancel;
    11     try
    12     {
    13         if (owner != null)
    14         {
    15             zero = Control.GetSafeHandle(owner);
    16         }
    17         if (zero == IntPtr.Zero)
    18         {
    19             zero = UnsafeNativeMethods.GetActiveWindow();
    20         }
    21         if (zero == IntPtr.Zero)
    22         {
    23             window = new NativeWindow();
    24             window.CreateHandle(new CreateParams());
    25             zero = window.Handle;
    26         }
    27         if (helpMsg == 0)
    28         {
    29             helpMsg = SafeNativeMethods.RegisterWindowMessage("commdlg_help");
    30         }
    31         NativeMethods.WndProc d = new NativeMethods.WndProc(this.OwnerWndProc);
    32         this.hookedWndProc = Marshal.GetFunctionPointerForDelegate(d);
    33         IntPtr userCookie = IntPtr.Zero;
    34         try
    35         {
    36             this.defOwnerWndProc = UnsafeNativeMethods.SetWindowLong(new HandleRef(this, zero), -4, d);
    37             if (Application.UseVisualStyles)
    38             {
    39                 userCookie = UnsafeNativeMethods.ThemingScope.Activate();
    40             }
    41             Application.BeginModalMessageLoop();
    42             try
    43             {
    44                 cancel = this.RunDialog(zero) ? DialogResult.OK : DialogResult.Cancel;
    45             }
    46             finally
    47             {
    48                 Application.EndModalMessageLoop();
    49             }
    50             return cancel;
    51         }
    52         finally
    53         {
    54             IntPtr windowLong = UnsafeNativeMethods.GetWindowLong(new HandleRef(this, zero), -4);
    55             if ((IntPtr.Zero != this.defOwnerWndProc) || (windowLong != this.hookedWndProc))
    56             {
    57                 UnsafeNativeMethods.SetWindowLong(new HandleRef(this, zero), -4, new HandleRef(this, this.defOwnerWndProc));
    58             }
    59             UnsafeNativeMethods.ThemingScope.Deactivate(userCookie);
    60             this.defOwnerWndProc = IntPtr.Zero;
    61             this.hookedWndProc = IntPtr.Zero;
    62             GC.KeepAlive(d);
    63         }
    64     }
    65     finally
    66     {
    67         if (window != null)
    68         {
    69             window.DestroyHandle();
    70         }
    71     }
    72     return cancel;
    73 }

    注意第36行代码:

    this.defOwnerWndProc = UnsafeNativeMethods.SetWindowLong(new HandleRef(this, zero), -4, d);

    上面的d指向这里:

     1 protected virtual IntPtr OwnerWndProc(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam)
     2 {
     3     if (msg != helpMsg)
     4     {
     5         return UnsafeNativeMethods.CallWindowProc(this.defOwnerWndProc, hWnd, msg, wparam, lparam);
     6     }
     7     if (NativeWindow.WndProcShouldBeDebuggable)
     8     {
     9         this.OnHelpRequest(EventArgs.Empty);
    10     }
    11     else
    12     {
    13         try
    14         {
    15             this.OnHelpRequest(EventArgs.Empty);
    16         }
    17         catch (Exception exception)
    18         {
    19             Application.OnThreadException(exception);
    20         }
    21     }
    22     return IntPtr.Zero;
    23 }

    它使用了自己的消息处理器替代了SaveFileDialog所属画面的消息处理器,然后把画面的消息处理器暂存在this.defOwnerWndProc中,然后在自己的消息处理中再转调画面的消息处理器(见上面第5行)。

    简单来说,Dialog拦截了Form的消息处理,在自己的消息处理器处理完后,再将消息分发会Form。

    基于以上处理,如果连续调两次ShowDialog会如何??

    第一次是OK的,但是当第二次执行第36行的代码时,SetWindowLong将会返回之前的处理器,即Dialog自己的消息处理器,然后保存在this.defOwnerWndProc中,等到Dialog自己的消息处理器处理完后,想要将消息再分发给Form时,this.defOwnerWndProc已经被修改为自己的消息处理器,然后就没有然后了。。。

  • 相关阅读:
    java 事件监听机制组成
    关于父进程和子进程的关系(UAC 绕过思路)
    Fort.js – 时尚、现代的进度提示效果
    Hive学习之函数DDL和Show、Describe语句
    js完美的div拖拽实例代码
    SSH2框架实现注冊发短信验证码实例
    再看C#中的托付和事件
    RGB(FFFFFF)转255:255:255
    单一目的聚集操作
    智慧城市,在中国的北海边再画一个圈——大连“中国首届智慧城市协同创新峰会”请你带好笔
  • 原文地址:https://www.cnblogs.com/royliugc/p/4846889.html
Copyright © 2020-2023  润新知