From be1b15b59f866edac935dc4e0f09d2ad2c2cae47 Mon Sep 17 00:00:00 2001 From: michalx2 Date: Thu, 7 Aug 2014 03:16:42 +0200 Subject: [PATCH] Fix single instance management There was a race condition in ApplicationInstanceManager. Now we use Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase to do instance management properly. --- HelpersLib/HelpersLib.csproj | 2 - .../ApplicationInstanceManager.cs | 122 ----------------- .../InstanceProxy.cs | 58 -------- ShareX/Forms/MainForm.cs | 9 +- ShareX/Program.cs | 127 +++++++++--------- ShareX/ShareX.csproj | 2 + ShareX/ShareXApplicationBase.cs | 24 ++++ 7 files changed, 93 insertions(+), 251 deletions(-) delete mode 100644 HelpersLib/SingleInstanceApplication/ApplicationInstanceManager.cs delete mode 100644 HelpersLib/SingleInstanceApplication/InstanceProxy.cs create mode 100644 ShareX/ShareXApplicationBase.cs diff --git a/HelpersLib/HelpersLib.csproj b/HelpersLib/HelpersLib.csproj index f158dc8e5..89e4d7ad4 100644 --- a/HelpersLib/HelpersLib.csproj +++ b/HelpersLib/HelpersLib.csproj @@ -293,8 +293,6 @@ - - UserControl diff --git a/HelpersLib/SingleInstanceApplication/ApplicationInstanceManager.cs b/HelpersLib/SingleInstanceApplication/ApplicationInstanceManager.cs deleted file mode 100644 index eb381f070..000000000 --- a/HelpersLib/SingleInstanceApplication/ApplicationInstanceManager.cs +++ /dev/null @@ -1,122 +0,0 @@ -#region License Information (GPL v3) - -/* - ShareX - A program that allows you to take screenshots and share any file type - Copyright (C) 2007-2014 ShareX Developers - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - Optionally you can also view the license at . -*/ - -#endregion License Information (GPL v3) - -using System; -using System.Diagnostics; -using System.Runtime.Remoting; -using System.Runtime.Remoting.Channels; -using System.Runtime.Remoting.Channels.Ipc; -using System.Threading; -using System.Windows.Forms; - -namespace SingleInstanceApplication -{ - public static class ApplicationInstanceManager - { - [DebuggerStepThrough] - public static bool CreateSingleInstance(string name, EventHandler callback) - { - string eventName = string.Format("{0}-{1}", Environment.MachineName, name); - - InstanceProxy.IsFirstInstance = false; - InstanceProxy.CommandLineArgs = Environment.GetCommandLineArgs(); - - try - { - using (EventWaitHandle eventWaitHandle = EventWaitHandle.OpenExisting(eventName)) - { - UpdateRemoteObject(name); - - if (eventWaitHandle != null) eventWaitHandle.Set(); - } - - Environment.Exit(0); - } - catch - { - InstanceProxy.IsFirstInstance = true; - - using (EventWaitHandle eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, eventName)) - { - ThreadPool.RegisterWaitForSingleObject(eventWaitHandle, WaitOrTimerCallback, callback, Timeout.Infinite, false); - } - - RegisterRemoteType(name); - } - - return InstanceProxy.IsFirstInstance; - } - - public static bool CreateSingleInstance(EventHandler callback) - { - try - { - return CreateSingleInstance(Application.ProductName, callback); - } - catch - { - } - - return true; - } - - private static void UpdateRemoteObject(string uri) - { - IpcClientChannel clientChannel = new IpcClientChannel(); - ChannelServices.RegisterChannel(clientChannel, true); - - InstanceProxy proxy = Activator.GetObject(typeof(InstanceProxy), string.Format("ipc://{0}{1}/{1}", Environment.MachineName, uri)) as InstanceProxy; - - if (proxy != null) - { - proxy.SetCommandLineArgs(InstanceProxy.IsFirstInstance, InstanceProxy.CommandLineArgs); - } - - ChannelServices.UnregisterChannel(clientChannel); - } - - private static void RegisterRemoteType(string uri) - { - IpcServerChannel serverChannel = new IpcServerChannel(Environment.MachineName + uri); - ChannelServices.RegisterChannel(serverChannel, true); - - RemotingConfiguration.RegisterWellKnownServiceType(typeof(InstanceProxy), uri, WellKnownObjectMode.Singleton); - - Process process = Process.GetCurrentProcess(); - process.Exited += delegate - { - ChannelServices.UnregisterChannel(serverChannel); - }; - } - - private static void WaitOrTimerCallback(object state, bool timedOut) - { - EventHandler callback = state as EventHandler; - if (callback == null) return; - - callback(state, new InstanceCallbackEventArgs(InstanceProxy.IsFirstInstance, InstanceProxy.CommandLineArgs)); - } - } -} \ No newline at end of file diff --git a/HelpersLib/SingleInstanceApplication/InstanceProxy.cs b/HelpersLib/SingleInstanceApplication/InstanceProxy.cs deleted file mode 100644 index 38b278787..000000000 --- a/HelpersLib/SingleInstanceApplication/InstanceProxy.cs +++ /dev/null @@ -1,58 +0,0 @@ -#region License Information (GPL v3) - -/* - ShareX - A program that allows you to take screenshots and share any file type - Copyright (C) 2007-2014 ShareX Developers - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License - as published by the Free Software Foundation; either version 2 - of the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - Optionally you can also view the license at . -*/ - -#endregion License Information (GPL v3) - -using System; -using System.Security.Permissions; - -namespace SingleInstanceApplication -{ - [Serializable] - [PermissionSet(SecurityAction.Demand, Name = "FullTrust")] - internal class InstanceProxy : MarshalByRefObject - { - public static bool IsFirstInstance { get; internal set; } - - public static string[] CommandLineArgs { get; internal set; } - - public void SetCommandLineArgs(bool isFirstInstance, string[] commandLineArgs) - { - IsFirstInstance = isFirstInstance; - CommandLineArgs = commandLineArgs; - } - } - - public class InstanceCallbackEventArgs : EventArgs - { - internal InstanceCallbackEventArgs(bool isFirstInstance, string[] commandLineArgs) - { - IsFirstInstance = isFirstInstance; - CommandLineArgs = commandLineArgs; - } - - public bool IsFirstInstance { get; private set; } - - public string[] CommandLineArgs { get; private set; } - } -} \ No newline at end of file diff --git a/ShareX/Forms/MainForm.cs b/ShareX/Forms/MainForm.cs index ac3814f79..e0617f0b3 100644 --- a/ShareX/Forms/MainForm.cs +++ b/ShareX/Forms/MainForm.cs @@ -41,7 +41,7 @@ namespace ShareX { public partial class MainForm : HotkeyForm { - public bool IsReady { get; private set; } + public ManualResetEvent ReadyWaitHandle { get; private set; } private bool forceClose; private UploadInfoManager uim; @@ -49,6 +49,7 @@ public partial class MainForm : HotkeyForm public MainForm() { + ReadyWaitHandle = new ManualResetEvent(false); InitControls(); HandleCreated += MainForm_HandleCreated; } @@ -62,7 +63,7 @@ private void MainForm_HandleCreated(object sender, EventArgs e) AutoCheckUpdate(); #endif - IsReady = true; + ReadyWaitHandle.Set(); DebugHelper.WriteLine("Startup time: {0} ms", Program.StartTimer.ElapsedMilliseconds); @@ -675,7 +676,7 @@ private void MainForm_Resize(object sender, EventArgs e) private void MainForm_LocationChanged(object sender, EventArgs e) { - if (IsReady && WindowState == FormWindowState.Normal) + if (ReadyWaitHandle.WaitOne(0) && WindowState == FormWindowState.Normal) { Program.Settings.MainFormPosition = Location; } @@ -683,7 +684,7 @@ private void MainForm_LocationChanged(object sender, EventArgs e) private void MainForm_SizeChanged(object sender, EventArgs e) { - if (IsReady && WindowState == FormWindowState.Normal) + if (ReadyWaitHandle.WaitOne(0) && WindowState == FormWindowState.Normal) { Program.Settings.MainFormSize = Size; } diff --git a/ShareX/Program.cs b/ShareX/Program.cs index bf6ef96bf..c8dd29f3b 100644 --- a/ShareX/Program.cs +++ b/ShareX/Program.cs @@ -24,7 +24,7 @@ #endregion License Information (GPL v3) using HelpersLib; -using SingleInstanceApplication; +using Microsoft.VisualBasic.ApplicationServices; using System; using System.Diagnostics; using System.IO; @@ -32,6 +32,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; using UploadersLib; @@ -228,7 +229,6 @@ public static string Title } } - public static bool IsMultiInstance { get; private set; } public static bool IsPortable { get; private set; } public static bool IsSilentRun { get; private set; } public static bool IsSandbox { get; private set; } @@ -247,6 +247,8 @@ public static string Title public static HotkeyManager HotkeyManager { get; set; } public static WatchFolderManager WatchFolderManager { get; set; } + private static ShareXApplicationBase _appBase; + [STAThread] private static void Main(string[] args) { @@ -255,17 +257,18 @@ private static void Main(string[] args) StartTimer = Stopwatch.StartNew(); - IsMultiInstance = CLIHelper.CheckArgs(args, "multi", "m"); - - if (IsMultiInstance || ApplicationInstanceManager.CreateSingleInstance(SingleInstanceCallback)) - { - Run(args); - } + bool isMultiInstance = CLIHelper.CheckArgs(args, "multi", "m"); + _appBase = new ShareXApplicationBase(!isMultiInstance); + _appBase.Startup += StartupHandler; + _appBase.StartupNextInstance += StartupNextInstanceHandler; + _appBase.Shutdown += ShutdownHandler; + _appBase.Run(args); } - private static void Run(string[] args) + private static void StartupHandler(object sender, StartupEventArgs e) { string appGuid = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString(); + string[] args = Environment.GetCommandLineArgs(); using (Mutex mutex = new Mutex(false, appGuid)) // Required for installer { @@ -291,9 +294,6 @@ private static void Run(string[] args) } } - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - DebugHelper.WriteLine("{0} started", Title); DebugHelper.WriteLine("Operating system: " + Environment.OSVersion.VersionString); DebugHelper.WriteLine("Command line: " + Environment.CommandLine); @@ -312,24 +312,64 @@ private static void Run(string[] args) DebugHelper.WriteLine("MainForm init started"); MainForm = new MainForm(); + _appBase.MainForm = MainForm; DebugHelper.WriteLine("MainForm init finished"); if (Settings == null) { SettingsResetEvent.WaitOne(); } - - Application.Run(MainForm); - - if (WatchFolderManager != null) WatchFolderManager.Dispose(); - SaveSettings(); - BackupSettings(); - - DebugHelper.WriteLine("ShareX closing"); - DebugHelper.Logger.SaveLog(LogsFilePath); } } + private static void StartupNextInstanceHandler(object sender, StartupNextInstanceEventArgs e) + { + e.BringToForeground = false; + string[] args = new string[e.CommandLine.Count + 1]; + args[0] = ""; + e.CommandLine.CopyTo(args, 1); + if (MainForm.ReadyWaitHandle.WaitOne(0)) DoStartupNextInstance(args); + else Task.Factory.StartNew(WaitFormLoad, args); + } + + private static void WaitFormLoad(object args) + { + try { + if (MainForm.ReadyWaitHandle.WaitOne(5000)) { + MainForm.Invoke(new Action(DoStartupNextInstance), args); + } + } + catch { } + } + + private static void DoStartupNextInstance(string[] args) + { + if (args == null || args.Length <= 1) { + if (MainForm.niTray != null && MainForm.niTray.Visible) { + // Workaround for Windows startup tray icon bug + MainForm.niTray.Visible = false; + MainForm.niTray.Visible = true; + } + + MainForm.ShowActivate(); + } + else if (MainForm.Visible) { + MainForm.ShowActivate(); + } + + MainForm.UseCommandLineArgs(args); + } + + private static void ShutdownHandler(object sender, EventArgs e) + { + if (WatchFolderManager != null) WatchFolderManager.Dispose(); + SaveSettings(); + BackupSettings(); + + DebugHelper.WriteLine("ShareX closing"); + DebugHelper.Logger.SaveLog(LogsFilePath); + } + public static void LoadSettings() { LoadProgramSettings(); @@ -435,7 +475,7 @@ private static void Application_ThreadException(object sender, ThreadExceptionEv OnError(e.Exception); } - private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + private static void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e) { OnError((Exception)e.ExceptionObject); } @@ -448,49 +488,6 @@ private static void OnError(Exception e) } } - private static void SingleInstanceCallback(object sender, InstanceCallbackEventArgs args) - { - if (WaitFormLoad(5000)) - { - Action d = () => - { - if (args.CommandLineArgs == null || args.CommandLineArgs.Length <= 1) - { - if (MainForm.niTray != null && MainForm.niTray.Visible) - { - // Workaround for Windows startup tray icon bug - MainForm.niTray.Visible = false; - MainForm.niTray.Visible = true; - } - - MainForm.ShowActivate(); - } - else if (MainForm.Visible) - { - MainForm.ShowActivate(); - } - - MainForm.UseCommandLineArgs(args.CommandLineArgs); - }; - - MainForm.InvokeSafe(d); - } - } - - private static bool WaitFormLoad(int wait) - { - Stopwatch timer = Stopwatch.StartNew(); - - while (timer.ElapsedMilliseconds < wait) - { - if (MainForm != null && MainForm.IsReady) return true; - - Thread.Sleep(10); - } - - return false; - } - public static void ConfigureUploadersConfigWatcher() { if (Program.Settings.DetectUploaderConfigFileChanges && uploaderConfigWatcher == null) diff --git a/ShareX/ShareX.csproj b/ShareX/ShareX.csproj index 10e2f20f7..11d654335 100644 --- a/ShareX/ShareX.csproj +++ b/ShareX/ShareX.csproj @@ -70,6 +70,7 @@ False ..\packages\MegaApiClient.1.0.4\lib\MegaApiClient.dll + False ..\packages\Newtonsoft.Json.6.0.3\lib\net40\Newtonsoft.Json.dll @@ -227,6 +228,7 @@ True Resources.resx + diff --git a/ShareX/ShareXApplicationBase.cs b/ShareX/ShareXApplicationBase.cs new file mode 100644 index 000000000..802a1a51f --- /dev/null +++ b/ShareX/ShareXApplicationBase.cs @@ -0,0 +1,24 @@ +using Microsoft.VisualBasic.ApplicationServices; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace ShareX +{ + class ShareXApplicationBase : WindowsFormsApplicationBase + { + public ShareXApplicationBase(bool isSingleInstance) + { + IsSingleInstance = isSingleInstance; + EnableVisualStyles = true; + } + + public new Form MainForm + { + get { return base.MainForm; } + set { base.MainForm = value; } + } + } +}