WindowsForm MouseUp fires twice when using Process.Start()

僤鯓⒐⒋嵵緔 提交于 2019-11-30 22:08:46

This is a re-entrancy bug, very similar to the kind of bugs you get when you use DoEvents() in your code. But it this case the bug is built into the OS. Instrumental for this problem is Explorer, it has a very unusual activation mode in this code. Something you can see when you look at the return value of Process.Start(), it is null. Happens when Process.Start() does not actually start a process.

You can see the bug in action by using a conditional break point that hits only the second time. With unmanaged debugging and the symbol server enabled, the call stack looks like this:

WindowsFormsApp1.exe!WindowsFormsApp1.Form1.listView1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) Line 19 C#
System.Windows.Forms.dll!System.Windows.Forms.Control.OnMouseUp(System.Windows.Forms.MouseEventArgs e) Line 9140    C#
System.Windows.Forms.dll!System.Windows.Forms.ListView.WndProc(ref System.Windows.Forms.Message m) Line 6298    C#
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.OnMessage(ref System.Windows.Forms.Message m) Line 14236  C#
System.Windows.Forms.dll!System.Windows.Forms.Control.ControlNativeWindow.WndProc(ref System.Windows.Forms.Message m) Line 14291    C#
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.DebuggableCallback(System.IntPtr hWnd, int msg, System.IntPtr wparam, System.IntPtr lparam) Line 780 C#
[Native to Managed Transition]  
user32.dll!__InternalCallWinProc@20()  Unknown
user32.dll!UserCallWinProcCheckWow()    Unknown
user32.dll!CallWindowProcW()    Unknown
comctl32.dll!_CallNextSubclassProc@20()    Unknown
comctl32.dll!_CallNextSubclassProc@20()    Unknown
comctl32.dll!_MasterSubclassProc@16()  Unknown
user32.dll!__InternalCallWinProc@20()  Unknown
user32.dll!UserCallWinProcCheckWow()    Unknown
user32.dll!DispatchMessageWorker()  Unknown
user32.dll!_DispatchMessageW@4()   Unknown
shell32.dll!SHProcessMessagesUntilEventsEx(struct HWND__ *,void * *,unsigned long,unsigned long,unsigned long,unsigned long)    Unknown
shell32.dll!CShellExecute::_RunThreadMaybeWait(bool)    Unknown
shell32.dll!CShellExecute::ExecuteNormal(struct _SHELLEXECUTEINFOW *)   Unknown
shell32.dll!ShellExecuteNormal(struct _SHELLEXECUTEINFOW *) Unknown
shell32.dll!_ShellExecuteExW@4()   Unknown
System.ni.dll!71db9903()    Unknown
[Frames below may be incorrect and/or missing, native debugger attempting to walk managed call stack]   
[Managed to Native Transition]  
System.dll!System.Diagnostics.ShellExecuteHelper.ShellExecuteFunction() Unknown
System.dll!System.Diagnostics.ShellExecuteHelper.ShellExecuteOnSTAThread()  Unknown
System.dll!System.Diagnostics.Process.StartWithShellExecuteEx(System.Diagnostics.ProcessStartInfo startInfo)    Unknown
System.dll!System.Diagnostics.Process.Start()   Unknown
System.dll!System.Diagnostics.Process.Start(System.Diagnostics.ProcessStartInfo startInfo)  Unknown
System.dll!System.Diagnostics.Process.Start(string fileName)    Unknown
WindowsFormsApp1.exe!WindowsFormsApp1.Form1.listView1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e) Line 19 C#

It is the SHProcessMessagesUntilEventsEx() function that is the evil-doer, the long way to spell "DoEvents" and implements the "maybe wait", it causes the dispatcher loop of your Winforms app to be re-entered. Detecting the MouseUp condition again and re-triggering the event handler.

You can't fix the OS but the workaround is the universal one for event handlers that cause re-entrancy problems, you can cleanly delay the tricky code with BeginInvoke() so that it runs immediately after the event is handled. Like this:

private void listView1_MouseUp(object sender, MouseEventArgs e) {
    this.BeginInvoke(new Action(() => {
        Process.Start(@"C:\");
    }));
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!