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