一个关于.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已经被修改为自己的消息处理器,然后就没有然后了。。。

posted @ 2015-09-29 17:35  RoyLiu  阅读(1769)  评论(6编辑  收藏  举报