一つはNetのSaveFileDialogコントロール(Winform)の興味深い問題

8116 ワード

シーン:winformのプログラムで、ある画面にButtonが置いてあり、このButtonをクリックすると呼び出されます.NetコントロールSaveFileDialogのShowDialogメソッド.
 
シーンは簡単ですが、このような面白い問題に遭遇しました.
機器が遅い場合は、上記のButtonを連続して2回クリックすると、スタックオーバーフロー異常(StackOverflowException)になります.
 
機器が遅い場合はシミュレーションが困難で安定して再現できないため、簡単なDemoを行い、Buttonのクリックイベントでまず非同期依頼でSaveFileDialogを1回調整してみた.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に配布します.
 
以上の処理に基づいて、2回連続してShowDialogを調整したらどうなりますか?
1回目はOKであるが、36行目のコードを2回目に実行すると、SetWindowLongは前のプロセッサであるDialog独自のメッセージプロセッサに戻り、thisに保存する.defOwnerWndProcでは、Dialog自身のメッセージプロセッサが処理した後、Formにメッセージを再配布しようとすると、this.defOwnerWndProcはすでに自分のメッセージプロセッサに変更され、その後はありません.の