Thursday, October 9, 2008

Restrict C# applications to single instance

I have seen many methods for doing this. But each one has its own pros & cons. So I thought of combining multiple approaches to implement a new one. In this approach, we will be using a mutex to make sure that only one instance of the application is running. Also, if the application is already running, we will try to bring it to the foreground.

We will be using few API calls for implementing this functionality. To start with, we will create a class Win32API for holding the API declarations.

using System.Runtime.InteropServices;

public class Win32API
{
  public enum WindowState : int
  {
     SW_NORMAL = 1,
     SW_MAXIMIZE = 3,
     SW_MINIMIZE = 6,
     SW_RESTORE = 9
  }

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

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

  [DllImport("user32.dll")]
  public static extern IntPtr GetLastActivePopup(IntPtr intPtWnd);

  [DllImport("user32.dll")]
  public static extern bool IsWindowEnabled(IntPtr intPtWnd);

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

We will use a GUID to identify the running application. The Main() is written as follows.

using System.Diagnostics;

static void Main()
{
    bool createdNow = true;

    /*
      * Create a Mutex object. A GUID is used as the name, to make sure that it is unique.
      */
    using (Mutex mutex = new Mutex(true, "{D21C4614-5252-4ea5-B38C-8C9A85035434}", out createdNow))
    {
        if (createdNow)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new frmMainWnd());
        }
        else
        {
            if (!ActivatePreviousInstance())
            {
                // If the attempt to bring the window to foreground fails,
                // just display a message.
                MessageBox.Show("Another instance of this application is already running",
                          "Information",
                          MessageBoxButtons.OK,
                          MessageBoxIcon.Information);
            }
        }
    }
}

private static bool ActivatePreviousInstance()
{
    bool success = false;

    try
    {
        using (Process current = Process.GetCurrentProcess())
        {
            foreach (Process proc in Process.GetProcessesByName(current.ProcessName))
            {
                // Get the process, that is not the current one (the previous instance)
                if (proc.Id != current.Id)
                {
                    IntPtr ptrWnd = proc.MainWindowHandle;

                    // Get handle to popup (child) window, if there is any
                    IntPtr ptrPopupWnd = Win32API.GetLastActivePopup(ptrWnd);

                    // If child window is found, it is the one to be brought to foreground
                    if (ptrPopupWnd != null && Win32API.IsWindowEnabled(ptrPopupWnd))
                    {
                        ptrWnd = ptrPopupWnd;
                    }

                    // Bring the window to foreground
                    if (!Win32API.SetForegroundWindow(proc.MainWindowHandle))
                        throw new Exception("SetForegroundWindow() failed");

                    // If the window is minimized, restore it on screen
                    if (Win32API.IsIconic(ptrWnd))
                    {
                        Win32API.ShowWindow(ptrWnd, (int)Win32API.WindowState.SW_RESTORE);
                    }

                    success = true;
                    break;
                }
            }
        }
    }
    catch (Exception ex)
    {
        Trace.WriteLine("Error [Program::ActivatePreviousInstance]: " + ex.ToString());
    }

    return success;
}

No comments: