diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index 53a42d7927..d04697de41 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -24,7 +24,6 @@ using System.Reflection; using System.Runtime.Loader; using System.Text; -using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Documents; @@ -47,7 +46,6 @@ public partial class App : Application { internal static CommandLineArguments CommandLineArguments; internal static readonly IList StartupExceptions = new List(); - internal static Mutex SingleInstanceMutex; public static ExportProvider ExportProvider { get; private set; } public static IExportProviderFactory ExportProviderFactory { get; private set; } @@ -64,38 +62,14 @@ public App() var cmdArgs = Environment.GetCommandLineArgs().Skip(1); App.CommandLineArguments = new CommandLineArguments(cmdArgs); + bool forceSingleInstance = (App.CommandLineArguments.SingleInstance ?? true) && !MiscSettingsPanel.CurrentMiscSettings.AllowMultipleInstances; if (forceSingleInstance) { - bool isFirst; - try - { - SingleInstanceMutex = new Mutex(initiallyOwned: true, @"Local\ILSpyInstance", out isFirst); - } - catch (WaitHandleCannotBeOpenedException) - { - isFirst = true; - } - if (!isFirst) - { - try - { - SingleInstanceMutex.WaitOne(10000); - } - catch (AbandonedMutexException) - { - // continue, there is no concurrent start happening. - } - } - cmdArgs = cmdArgs.Select(FullyQualifyPath); - string message = string.Join(Environment.NewLine, cmdArgs); - if (SendToPreviousInstance("ILSpy:\r\n" + message, !App.CommandLineArguments.NoActivate)) - { - ReleaseSingleInstanceMutex(); - Environment.Exit(0); - } + SingleInstanceHandling.ForceSingleInstance(cmdArgs); } + InitializeComponent(); Resources.RegisterDefaultStyles(); @@ -114,20 +88,6 @@ public App() ILSpyTraceListener.Install(); } - internal static void ReleaseSingleInstanceMutex() - { - var mutex = SingleInstanceMutex; - SingleInstanceMutex = null; - if (mutex == null) - { - return; - } - using (mutex) - { - mutex.ReleaseMutex(); - } - } - static Assembly ResolvePluginDependencies(AssemblyLoadContext context, AssemblyName assemblyName) { var rootPath = Path.GetDirectoryName(typeof(App).Assembly.Location); @@ -210,22 +170,6 @@ protected override void OnStartup(StartupEventArgs e) base.OnStartup(e); } - string FullyQualifyPath(string argument) - { - // Fully qualify the paths before passing them to another process, - // because that process might use a different current directory. - if (string.IsNullOrEmpty(argument) || argument[0] == '/') - return argument; - try - { - return Path.Combine(Environment.CurrentDirectory, argument); - } - catch (ArgumentException) - { - return argument; - } - } - void DotNet40_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) { // On .NET 4.0, an unobserved exception in a task terminates the process unless we mark it as observed @@ -282,67 +226,6 @@ internal static void UnhandledException(Exception exception) } #endregion - #region Pass Command Line Arguments to previous instance - bool SendToPreviousInstance(string message, bool activate) - { - string ownProcessName; - using (var ownProcess = Process.GetCurrentProcess()) - { - ownProcessName = ownProcess.ProcessName; - } - - bool success = false; - NativeMethods.EnumWindows( - (hWnd, lParam) => { - string windowTitle = NativeMethods.GetWindowText(hWnd, 100); - if (windowTitle.StartsWith("ILSpy", StringComparison.Ordinal)) - { - string processName = NativeMethods.GetProcessNameFromWindow(hWnd); - Debug.WriteLine("Found {0:x4}: '{1}' in '{2}'", hWnd, windowTitle, processName); - if (string.Equals(processName, ownProcessName, StringComparison.OrdinalIgnoreCase)) - { - IntPtr result = Send(hWnd, message); - Debug.WriteLine("WM_COPYDATA result: {0:x8}", result); - if (result == (IntPtr)1) - { - if (activate) - NativeMethods.SetForegroundWindow(hWnd); - success = true; - return false; // stop enumeration - } - } - } - return true; // continue enumeration - }, IntPtr.Zero); - return success; - } - - unsafe static IntPtr Send(IntPtr hWnd, string message) - { - const uint SMTO_NORMAL = 0; - - CopyDataStruct lParam; - lParam.Padding = IntPtr.Zero; - lParam.Size = message.Length * 2; - fixed (char* buffer = message) - { - lParam.Buffer = (IntPtr)buffer; - IntPtr result; - // SendMessage with 3s timeout (e.g. when the target process is stopped in the debugger) - if (NativeMethods.SendMessageTimeout( - hWnd, NativeMethods.WM_COPYDATA, IntPtr.Zero, ref lParam, - SMTO_NORMAL, 3000, out result) != IntPtr.Zero) - { - return result; - } - else - { - return IntPtr.Zero; - } - } - } - #endregion - void Window_RequestNavigate(object sender, RequestNavigateEventArgs e) { ILSpy.MainWindow.Instance.NavigateTo(e); diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 2ead2c7e02..aa8eb92b2b 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -581,7 +581,7 @@ protected override void OnSourceInitialized(EventArgs e) { hwndSource.AddHook(WndProc); } - App.ReleaseSingleInstanceMutex(); + SingleInstanceHandling.ReleaseSingleInstanceMutex(); // Validate and Set Window Bounds Rect bounds = Rect.Transform(sessionSettings.WindowBounds, source.CompositionTarget.TransformToDevice); var boundsRect = new System.Drawing.Rectangle((int)bounds.Left, (int)bounds.Top, (int)bounds.Width, (int)bounds.Height); diff --git a/ILSpy/SingleInstanceHandling.cs b/ILSpy/SingleInstanceHandling.cs new file mode 100644 index 0000000000..5a59f1ee25 --- /dev/null +++ b/ILSpy/SingleInstanceHandling.cs @@ -0,0 +1,154 @@ +// Copyright (c) 2022 AlphaSierraPapa for the SharpDevelop Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; + +namespace ICSharpCode.ILSpy +{ + internal static class SingleInstanceHandling + { + internal static Mutex SingleInstanceMutex; + + internal static void ForceSingleInstance(IEnumerable cmdArgs) + { + bool isFirst; + try + { + SingleInstanceMutex = new Mutex(initiallyOwned: true, @"Local\ILSpyInstance", out isFirst); + } + catch (WaitHandleCannotBeOpenedException) + { + isFirst = true; + } + if (!isFirst) + { + try + { + SingleInstanceMutex.WaitOne(10000); + } + catch (AbandonedMutexException) + { + // continue, there is no concurrent start happening. + } + } + cmdArgs = cmdArgs.Select(FullyQualifyPath); + string message = string.Join(Environment.NewLine, cmdArgs); + if (SendToPreviousInstance("ILSpy:\r\n" + message, !App.CommandLineArguments.NoActivate)) + { + ReleaseSingleInstanceMutex(); + Environment.Exit(0); + } + } + + internal static string FullyQualifyPath(string argument) + { + // Fully qualify the paths before passing them to another process, + // because that process might use a different current directory. + if (string.IsNullOrEmpty(argument) || argument[0] == '/') + return argument; + try + { + return Path.Combine(Environment.CurrentDirectory, argument); + } + catch (ArgumentException) + { + return argument; + } + } + + internal static void ReleaseSingleInstanceMutex() + { + var mutex = SingleInstanceMutex; + SingleInstanceMutex = null; + if (mutex == null) + { + return; + } + using (mutex) + { + mutex.ReleaseMutex(); + } + } + + #region Pass Command Line Arguments to previous instance + internal static bool SendToPreviousInstance(string message, bool activate) + { + string ownProcessName; + using (var ownProcess = Process.GetCurrentProcess()) + { + ownProcessName = ownProcess.ProcessName; + } + + bool success = false; + NativeMethods.EnumWindows( + (hWnd, lParam) => { + string windowTitle = NativeMethods.GetWindowText(hWnd, 100); + if (windowTitle.StartsWith("ILSpy", StringComparison.Ordinal)) + { + string processName = NativeMethods.GetProcessNameFromWindow(hWnd); + Debug.WriteLine("Found {0:x4}: '{1}' in '{2}'", hWnd, windowTitle, processName); + if (string.Equals(processName, ownProcessName, StringComparison.OrdinalIgnoreCase)) + { + IntPtr result = Send(hWnd, message); + Debug.WriteLine("WM_COPYDATA result: {0:x8}", result); + if (result == (IntPtr)1) + { + if (activate) + NativeMethods.SetForegroundWindow(hWnd); + success = true; + return false; // stop enumeration + } + } + } + return true; // continue enumeration + }, IntPtr.Zero); + return success; + } + + unsafe static IntPtr Send(IntPtr hWnd, string message) + { + const uint SMTO_NORMAL = 0; + + CopyDataStruct lParam; + lParam.Padding = IntPtr.Zero; + lParam.Size = message.Length * 2; + fixed (char* buffer = message) + { + lParam.Buffer = (IntPtr)buffer; + IntPtr result; + // SendMessage with 3s timeout (e.g. when the target process is stopped in the debugger) + if (NativeMethods.SendMessageTimeout( + hWnd, NativeMethods.WM_COPYDATA, IntPtr.Zero, ref lParam, + SMTO_NORMAL, 3000, out result) != IntPtr.Zero) + { + return result; + } + else + { + return IntPtr.Zero; + } + } + } + #endregion + } +}