一個關於.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已經被修改為自己的消息處理器,然后就沒有然后了。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM