12
\$\begingroup\$

I have created the following class that is used to provide single instancing functionality to an application. It is called like:

if (Helpers.SingleInstance.IsAlreadyRunning())
{
    Helpers.SingleInstance.ShowRunningApp();
    return;
}

The idea of the class is to prevent the program from being ran twice and it does this by using the applications Guid, as it is a C# program these should always be present. It also allows for extra functionality so a user can override the Guid, this is useful if the same program has different modes it can be ran in so they can both run at the same time.

Lastly it provides the functionality to show the running app, either from the Guid, or from strings contained in the applications main window title.

Here's the class:

using System;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
using System.Windows.Forms;

namespace Helpers
{
    using static User32;

    /// <summary>
    /// A class that provides functionality for single instancing an application.
    /// </summary>
    public static class SingleInstance
    {
        #region Constants

        private const uint WINDOW_FLASH_COUNT = 6;
        private const uint WINDOW_FLASH_RATE = 70;

        #endregion

        #region Fields

        private static string __appGuid;

        private static Mutex _mutex;

        #endregion

        #region PublicProperties

        /// <summary>
        /// If true the application checks if it is running only against the current user, else it checks against all users.
        /// </summary>
        public static bool ThisUserOnly { get; set; } = true;

        #endregion

        #region PrivateProperties

        private static string AppGuid
        {
            get
            {
                return __appGuid;
            }
            set
            {
                __appGuid = value;

                CreateMutex();
            }
        }

        #endregion

        #region Constructor

        static SingleInstance()
        {
            AppGuid = GetAssemblyGuid(Assembly.GetEntryAssembly());
        }

        #endregion

        #region PublicMethods

        /// <summary>
        /// Overrides the application guid to check against from the supplied text.
        /// </summary>
        /// <param name="text">The text to modify the guid with.</param>
        public static void OverrideAppGuid(string text)
        {
            using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
            {
                OverrideAppGuid(new Guid(md5.ComputeHash(System.Text.Encoding.Default.GetBytes(text))));
            }
        }

        /// <summary>
        /// Overrides the application guid to check against with the supplied one.
        /// </summary>
        /// <param name="newGuid">The guid to replace the current one with.</param>
        public static void OverrideAppGuid(Guid newGuid)
        {
            AppGuid = newGuid.ToString();
        }

        /// <summary>
        /// Checks if the application is already running or not.
        /// </summary>
        /// <returns>A bool indicating whether or not the application is running.</returns>
        public static bool IsAlreadyRunning()
        {
            try
            {
                if (!_mutex.WaitOne(0, false))
                {
                    return true;
                }
            }
            catch (AbandonedMutexException) { }

            return false;
        }

        /// <summary>
        /// Shows the running application with the same guid as the application that calls it.
        /// </summary>
        public static void ShowRunningApp()
        {
            Process current = Process.GetCurrentProcess();
            foreach (Process process in Process.GetProcesses())
            {
                if (process.Id == current.Id)
                {
                    continue;
                }

                try
                {
                    Assembly assembly = Assembly.LoadFrom(process.MainModule.FileName);

                    string processGuid = GetAssemblyGuid(assembly);
                    if (AppGuid.Equals(processGuid))
                    {
                        ShowProcess(process);
                        return;
                    }
                }
                catch { } //Is likely to throw an exception so just ignore it
            }
        }

        /// <summary>
        /// Shows a running application from the supplied title parts.
        /// </summary>
        /// <param name="titleParts">An array of strings, all of which must be present in the title of the window.</param>
        public static void ShowRunningApp(params string[] titleParts)
        {
            Process current = Process.GetCurrentProcess();
            foreach (Process process in Process.GetProcesses())
            {
                if (process.Id == current.Id)
                {
                    continue;
                }

                bool showProcess = true;
                foreach (string titlePart in titleParts)
                {
                    if (!process.MainWindowTitle.Contains(titlePart))
                    {
                        showProcess = false;
                        break;
                    }
                }

                if (showProcess)
                {
                    ShowProcess(process);
                    return;
                }
            }
        }

        #endregion

        #region PrivateMethods

        private static string GetAssemblyGuid(Assembly assembly)
        {
            object[] customAttribs = assembly.GetCustomAttributes(typeof(GuidAttribute), false);
            if (customAttribs.Length < 1)
            {
                return null;
            }

            return ((GuidAttribute)customAttribs.GetValue(0)).Value;
        }

        private static void CreateMutex()
        {
            if (_mutex?.WaitOne(0, false) ?? false)
            {
                _mutex.ReleaseMutex();
            }
            _mutex?.Close();
            _mutex?.Dispose();

            string mutexId = $"{(ThisUserOnly ? "" : "Global\\")}{{{AppGuid}}}";

            MutexAccessRule allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), MutexRights.FullControl, AccessControlType.Allow);
            MutexSecurity securitySettings = new MutexSecurity();
            securitySettings.AddAccessRule(allowEveryoneRule);

            bool createdNew;
            _mutex = new Mutex(false, mutexId, out createdNew, securitySettings);
        }

        private static void ShowProcess(Process process)
        {
            if (IsIconic(process.MainWindowHandle))
            {
                ShowWindow(process.MainWindowHandle, SW_RESTORE);
            }

            MoveWindowToScreen(process);

            SetForegroundWindow(process.MainWindowHandle);
            FlashWindow(process.MainWindowHandle);
        }

        private static void MoveWindowToScreen(Process process)
        {
            RECT rect;
            if (!GetWindowRect(new HandleRef(process, process.MainWindowHandle), out rect))
            {
                return;
            }

            Rectangle windowRect = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left + 1, rect.Bottom - rect.Top + 1);

            Screen screen = Screen.AllScreens.Where(s => s.Bounds.IntersectsWith(windowRect))
                                             .Select(s => new { Screen = s, Intersection = Rectangle.Intersect(s.WorkingArea, windowRect) })
                                             .OrderByDescending(o => o.Intersection.Width * o.Intersection.Height)
                                             .Select(o => o.Screen)
                                             .First();

            CentreWindowToScreen(process.MainWindowHandle, screen, rect);
        }

        private static void CentreWindowToScreen(IntPtr handle, Screen screen, RECT rect)
        {
            int width = rect.Right - rect.Left;
            int x = screen.WorkingArea.X + ((screen.WorkingArea.Width / 2) - (width / 2));
            int height = rect.Bottom - rect.Top;
            int y = screen.WorkingArea.Y + ((screen.WorkingArea.Height / 2) - (height / 2));

            SetWindowPos(handle, 0, x, y, width, height, SWP_NOZORDER | SWP_NOSIZE | SWP_SHOWWINDOW);
        }

        private static bool FlashWindow(IntPtr handle)
        {
            FLASHWINFO fi = new FLASHWINFO();
            fi.cbSize = Convert.ToUInt32(Marshal.SizeOf(fi));
            fi.hwnd = handle;
            fi.dwFlags = FLASHW_ALL;
            fi.uCount = WINDOW_FLASH_COUNT;
            fi.dwTimeout = WINDOW_FLASH_RATE;

            return FlashWindowEx(ref fi);
        }

        #endregion
    }
}

And it uses the following helper class, it pretty much just wraps user32.dll methods into a common class to not clutter the main class:

using System;
using System.Runtime.InteropServices;

namespace Helpers
{
    internal static class User32
    {
        #region DLLImports

        [DllImport("user32.dll")]
        public static extern bool IsIconic(IntPtr handle);

        [DllImport("user32.dll")]
        public static extern bool ShowWindow(IntPtr handle, int nCmdShow);

        [DllImport("user32.dll")]
        public static extern bool SetForegroundWindow(IntPtr handle);

        [DllImport("user32.dll")]
        public static extern bool GetWindowRect(HandleRef hWnd, out RECT lpRect);

        [DllImport("user32.dll")]
        public static extern IntPtr SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);

        [DllImport("user32.dll")]
        public static extern bool FlashWindowEx(ref FLASHWINFO pwfi);

        #endregion

        #region Structs

        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct FLASHWINFO
        {
            public uint cbSize;
            public IntPtr hwnd;
            public uint dwFlags;
            public uint uCount;
            public uint dwTimeout;
        }

        #endregion

        #region Constants

        public const int SW_RESTORE = 9;

        public const short SWP_NOSIZE = 1;
        public const short SWP_NOZORDER = 0X4;
        public const int SWP_SHOWWINDOW = 0x0040;

        public const uint FLASHW_ALL = 3;

        #endregion
    }
}

I appreciate any comments on it.

\$\endgroup\$
5
  • 2
    \$\begingroup\$ public static class SingleInstance this class is static so calling it an instance is not quite right. Did you mean to implement a Singleton? The other User32 class should be static but it isn't. You did it exactly the the wrong way round ;-) \$\endgroup\$
    – t3chb0t
    Commented Feb 8, 2017 at 17:57
  • \$\begingroup\$ @t3chb0t I just forgot to make it static and instance refers to single instance of the calling app \$\endgroup\$ Commented Feb 8, 2017 at 18:27
  • 1
    \$\begingroup\$ This is a very sophisticated solution. Does it have any signifficant advantages over a simple mutex (which I'm also using)? \$\endgroup\$
    – t3chb0t
    Commented Feb 8, 2017 at 19:06
  • \$\begingroup\$ @t3chb0t On my phone right now but google for the correct way to do it and the top answer is what I based this off and theb altered a tad. It also has the extra functionality of showing etc. And you can override the guid which we used with an application that can be used for multiple things. \$\endgroup\$ Commented Feb 8, 2017 at 20:10
  • 1
    \$\begingroup\$ @t3chb0t It is based off of this answer \$\endgroup\$ Commented Feb 9, 2017 at 8:51

2 Answers 2

12
+50
\$\begingroup\$

I've read through this several times, and I see a couple things that bother me.

First off, this class name just doesn't make sense. I hate to say it, but it really doesn't. When I see class SingleInstance I immediately think Singleton which is not what this is, this is an InstanceManager.

So now that we've named it InstanceManager (or something similar, that was the first name that came to mind that described what it did) we need to look at the API a bit, because some of that rubs me the wrong way as well.

if (Helpers.InstanceManager.IsAlreadyRunning())
{
    Helpers.InstanceManager.ShowRunningApp();
    return;
}

Technically IsAlreadyRunning is not checking for this app, but another app of the same type. So it should be IsInstanceRunning(), then the verbiage makes a little more sense.

Once we clear that up, there's an obvious enhancement that could be made to the API:

public static void ShowIfInstanceRunning()
{
    if (IsInstanceRunning())
    {
        ShowRunningApp();
    }
}

This means in cases where we don't need to return or anything if the other is running we can simply do:

Helpers.InstanceManager.ShowIfInstanceRunning()

Looking more into the API I really don't like the idea of having the ThisUserOnly (which should be CurrentUserOnly) be a property, as well as the AppGuid and requiring an OverrideGuid method. That's just ugly.

Instead, you could supply a Guid? parameter to various methods, if guid.HasValue == false then use the AppGuid, if guid.HasValue == true then use guid.Value. This eliminates the need for OverrideGuid, which is a very ugly API.

If you make these changes then this:

public static void OverrideAppGuid(string text)
{
    using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
    {
        OverrideAppGuid(new Guid(md5.ComputeHash(System.Text.Encoding.Default.GetBytes(text))));
    }
}

Becomes:

public static Guid GenerateHashedGuid(string text)
{
    using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
    {
        return new Guid(md5.ComputeHash(System.Text.Encoding.Default.GetBytes(text)));
    }
}

Just as well, ThisUserOnly should be a parameter bool currentUserOnly = true on the appropriate methods.


If you don't want to switch those properties out for parameters, then you should make this an instance class. I see absolutely zero work inside it that could not be done in an instantiated class. Then you can define a constructor InstanceManager(Guid) that you can attach directly to the appropriate application so that if a user wants to check for multiple applications running, it's as simple as:

var thisInstanceManager = new InstanceManager();
var xInstanceManager = new InstanceManager(xGuid);
\$\endgroup\$
5
  • \$\begingroup\$ ShowIfRunning() sounds like a command but returns a bool; makes for awkward syntax e.g. if (ShowIfRunning()), does false mean it didn't show, it's not running, or some other failure? \$\endgroup\$
    – 404
    Commented Feb 16, 2017 at 17:57
  • \$\begingroup\$ @eurotrash Meh, that's what XML comments are for. <summary>Returns true if the application is running, false otherwise.</summary> Personally, I think this API is severely clunky and, in general, feels very unusable. \$\endgroup\$ Commented Feb 16, 2017 at 18:00
  • \$\begingroup\$ I'd prefer to have ShowIfInstanceRunning as two separate methods still, leave it to the caller if they want to show the app... not every one will. \$\endgroup\$ Commented Feb 17, 2017 at 9:00
  • \$\begingroup\$ Taking this on board I now have something like Helpers.InstanceManager intanceManager = new Helpers.InstanceManager(null /*, optional currentUserOnly here*/); if (intanceManager.IsInstanceRunning()) { intanceManager.ShowRunningInstance(); return; } which does seem to read better. \$\endgroup\$ Commented Feb 17, 2017 at 9:29
  • \$\begingroup\$ Side note: Since I made the class "non static" (that doesn't quite read right...), I have also made it sealed \$\endgroup\$ Commented Feb 17, 2017 at 9:52
3
\$\begingroup\$

SRP

What botthers me most in the current implementation is that the SingleInstance class contains a lot of not related features. I find you can split it up into four smaller classes.

Currently it's not only hard to test them but some of them are good candidates to be reused in other projects.

This is what I suggest...


The first class would be really small and would only take care of creating hashed-guids.

class GuidFactory
{
    public static Guid CreateHashedGuid(string text)
    {
        using (var md5 = System.Security.Cryptography.MD5.Create())
        {
            return new Guid(md5.ComputeHash(System.Text.Encoding.Default.GetBytes(text)));
        }
    }
}

Now come a bunch of managers. I don't like the term Manager but I don't have a better idea for their names.

The managers are not complete but just rough examples.


The first manager is the InstanceManager. It only knows how to check if an application is running.

class InstanceManager
{
    private static string __appGuid;

    private static Mutex _mutex;

    private static string AppGuid
    {
        get
        {
            return __appGuid;
        }
        set
        {
            __appGuid = value;

            CreateMutex();
        }
    }

    public InstanceManager()
    {
        // ...
    }

    /// <summary>
    /// Checks if the application is already running or not.
    /// </summary>
    /// <returns>A bool indicating whether or not the application is running.</returns>
    public static bool IsAlreadyRunning()
    {
        try
        {
            if (!_mutex.WaitOne(0, false))
            {
                return true;
            }
        }
        catch (AbandonedMutexException) { }

        return false;
    }
}

The next manager is the ProcessManager. Currently it only knows how to find a process by guid.

class ProcessManager
{
    public Process FindProcess(Guid guid)
    {
        // find process by guid...
    }
}

The last manager is the WindowManager. It can do things with windows.

class WindowManager
{
    private const uint WINDOW_FLASH_COUNT = 6;
    private const uint WINDOW_FLASH_RATE = 70;

    private readonly Guid _applicationGuid;
    
    public WindowManager(Guid applicationGuid)
    {
        _applicationGuid = applicationGuid;
    }
    
    public void Activate()
    {
        // ...
    }   

    private static string GetAssemblyGuid(Assembly assembly)
    {
        // ...
    }   

    private static void ShowProcess(Process process)
    {
        // ...
    }

    private static void MoveWindowToScreen(Process process)
    {
        // ...
    }

    private static void CentreWindowToScreen(IntPtr handle, Screen screen, RECT rect)
    {
        // ...
    }

    private static bool FlashWindow(IntPtr handle)
    {
        // ...
    }   
}

regions

I'm not a fan of grouping methods or properties etc. with regions. Usually it's an indicator that the class is to big and needs to be devided into more specialized units with only a few methods where grouping them does not make sense any more.

\$\endgroup\$
3
  • \$\begingroup\$ On the regions comment: Its one of the standards we have at work, and it does make it easier to find all the methods etc than to have to browse the code. \$\endgroup\$ Commented Feb 17, 2017 at 12:53
  • \$\begingroup\$ @TheLethalCoder if you need regions then your classes are probably often too big. In single-responsibility classes you don't need them. Also better then regions is a standard where kind of sort the fields, constructors, properties etc like, fields first, then constructors, then properties then methods where the most abstract ones are at the top and the most specialized at the bottom. \$\endgroup\$
    – t3chb0t
    Commented Feb 17, 2017 at 12:59
  • 1
    \$\begingroup\$ We do sort them by a common order, our classes are probably a bit too big. Either way its a coding standard so I can't change that. \$\endgroup\$ Commented Feb 17, 2017 at 13:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.