Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Singleton application in C# with redirection of arguments.

0.00/5 (No votes)
8 May 2008 1  
A Singleton application in C# with redirection of arguments.

Introduction

This code contains a class named SingletonApplication that provides a method of ensuring that only one instance of an application is allowed to load, while at the same time providing a means to use the arguments passed to the second instance in the first instance.

Background

While working on another application, I ran into a situation where I needed only one instance of the application to load, and where I could find out what file the user was trying to open and use that information in the first/main instance of the application. After trying to develop my own solution to no avail, and then searching for a solution to this problem, I came across many different ways to do it, one of which included using the WM_COPYDATA message to serialize the data and deserialize it in the first instance... I was unable to get this to work in Vista, and decided to make another attempt at writing my own solution for this; however, instead of using memory to transmit the information, I used the Registry.

Using the Code

Below is an example of how SingletonApplication could be used in your application. Since we will be passing the startup arguments of the second instance in, I separate the code that will use the arguments into a separate method. First, you pass the arguments and an identifier to SingletonApplication.Instance(args, "YOUR-ID-HERE"); if SingletonApplication.Instance = true, that means this is the first instance, and you can proceed as your normally would, making sure to register with the SingletonApplication.SecondInstanceLoaded event, and if SingletonApplication.Instance = false, quit the application (if you want).

using System;
using System.Windows.Forms;
using Azad.Ratzki.Utils;

namespace WindowsFormsApplication1
{

    static class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            // The "ANYTHING-YOU-WANT-HERE" will become
            // the identification the connects your two instances.
            // If SingletonApplication.Instance == true then this
            // is the first instance, otherwise you are trying to load a second instance. 
            if (SingletonApplication.Instance(args, "ANYTHING-YOU-WANT-HERE"))
            {
                Application.EnableVisualStyles();
                Application.SetCompatibleTextRenderingDefault(false);
                Application.Run(new Form1());

                // Connect to the SecondInstanceLoaded event so you can
                // tell when a second instance tries to load and process its arguments.
                SingletonApplication.SecondInstanceLoaded += new 
                  EventHandler<SingletonApplicationEventArgs>(
                  SingletonApplication_SecondInstanceLoaded);
            }
            else
            {
                Application.Exit();
            }
        }

        static void SingletonApplication_SecondInstanceLoaded(object sender, 
                                         SingletonApplicationEventArgs e)
        {
            // Pass the arguments you receive from the second instance
            // to a method of your choosing that will use the arguments.
            ParseArgs(e.Args);
        }

        static void ParseArgs(string[] args)
        {    
            //Do something w/ the arguments.
        }
    }
}

Code Explained

SingletonApplication relies on the Mutex class to determine whether the instance of the application being loaded is the first or not. For transmitting the arguments to the first instance, I use the built-in Registry .NET classes, and created two methods GetArgs() and SetArgs(string[] args) to store and retrieve the arguments from the Registry. Finally, to nudge the first instance and get it to retrieve the arguments, I use a NativeWindow derived class called SpecialNativeWindow to catch a window message, causing an event to bubble up to the SingletonApplication class, where we call GetArgs() and pass the retrieved string array into a new SingletonApplicationEventArgs and fire the SecondInstanceLoaded event.

using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using Microsoft.Win32;

namespace Azad.Ratzki.Utils
{
    /// <summary>
    /// Provides a way to ensure that only one instance of an application
    /// is loaded and if a second instance tries to load provides
    /// it's arguments to the first instance.
    /// </summary>
    class SingletonApplication : IDisposable
    {
        #region PInvoke
        [DllImport("user32.dll", EntryPoint = 
                   "FindWindow", SetLastError = true)]
        static extern System.IntPtr FindWindowByCaption(int ZeroOnly, 
                                    string lpWindowName);
        [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern bool SendNotifyMessage(IntPtr hWnd, int Msg, 
                           IntPtr wParam, IntPtr lParam);
        #endregion

        #region Events
        /// <summary>
        /// Fires when a Second Instance is loaded.
        /// </summary>
        public static event 
          EventHandler<SingletonApplicationEventArgs> SecondInstanceLoaded;
        #endregion

        #region Fields
        SpecialNativeWindow sw;
        static SingletonApplication _instance;
        static Mutex myMutex;
        static string appID;
        #endregion

        #region Constants
        /// <summary>
        /// Custom/Fake message # to wake up the NativeWindow.
        /// </summary>
        const int CMESSAGE = 93956;
        #endregion

        #region Constructor
        /// <summary>
        /// Private constructor, use SingletonApplication.Instance.
        /// </summary>
        SingletonApplication() { }
        #endregion

        #region Methods
        /// <summary>
        /// The first method that should be called when using SingletonApplication.
        /// </summary>
        /// <param name="args">Pass the startup arguments to this.</param>
        /// <param name="sharedID">Can be anything you
        /// choose, must be the same in both instances.</param>
        /// <returns>Returns true if it is the first instance
        /// otherwise returns false.</returns>
        public static bool Instance(string[] args, string sharedID)
        {
            _instance = new SingletonApplication();
            SetArgs(args);
            bool owned = false;
            myMutex = new Mutex(true, sharedID, out owned);
            appID = sharedID;
            GC.KeepAlive(myMutex);
            if (owned)
            {
                _instance.sw = new SpecialNativeWindow(sharedID);
                _instance.sw.ApplicationOpened += 
                   new EventHandler<EventArgs>(sw_ApplicationOpened);
                GC.KeepAlive(_instance);
                GC.KeepAlive(_instance.sw);
            }
            else
            {
                IntPtr foundWin = FindWindowByCaption(0, sharedID);
                SendNotifyMessage(foundWin, CMESSAGE, IntPtr.Zero, IntPtr.Zero);
            }

            return owned;
        }
        /// <summary>
        /// Fired from SpecialNativeWindow notifying
        /// us the a second instance tried to load.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        static void sw_ApplicationOpened(object sender, EventArgs e)
        {
            _instance.OnSecondInstanceLoaded();
        }
        /// <summary>
        /// Fires when we receive sw_ApplicationOpened from
        /// SpecialNativeWindow and sends the arguments sent to that second instance
        /// via SingletonApplicationEventArgs.Args for the primary instance to use.
        /// </summary>
        protected void OnSecondInstanceLoaded()
        {
            if (SecondInstanceLoaded != null)
            {
                SecondInstanceLoaded(this, new SingletonApplicationEventArgs(GetArgs()));
            }
        }
        /// <summary>
        /// Used to retrieve the arguments passed
        /// to the second instance of the application.
        /// </summary>
        /// <returns>A string[] array containing the arguments.</returns>
        static string[] GetArgs()
        {
            try
            {
                string[] tempString = (string[])
                  Registry.CurrentUser.OpenSubKey(@"Software\" + 
                  appID, false).GetValue("Args", null);
                Registry.CurrentUser.OpenSubKey("Software", 
                                                true).DeleteSubKey(appID);
                return tempString;
            }
            catch
            {
                return null;
            }
        }
        /// <summary>
        /// Used to store arguments in the registry temporarily.
        /// </summary>
        /// <param name="args"></param>
        static void SetArgs(string[] args)
        {
            Registry.CurrentUser.OpenSubKey("Software", 
               true).CreateSubKey(appID).SetValue("Args", args);
        }
        #endregion

        #region MessengerWindow
        /// <summary>
        /// NativeWindow to process messages.
        /// </summary>
        class SpecialNativeWindow : NativeWindow
        {
            public event EventHandler<EventArgs> ApplicationOpened;
            private void OnApplicationOpened()
            {
                if (ApplicationOpened != null)
                {
                    ApplicationOpened(this, EventArgs.Empty);
                }
            }
            protected override void WndProc(ref Message m)
            {
                switch (m.Msg)
                {
                    case CMESSAGE:
                        OnApplicationOpened();
                        break;
                    default:
                        break;
                }
                base.WndProc(ref m);
            }
            public SpecialNativeWindow(string ID)
            {
                CreateParams cp = new CreateParams();
                cp.Caption = ID;
                this.CreateHandle(cp);
            }
        }
        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            if (myMutex != null)
            {
                myMutex.ReleaseMutex();
            }

            if (_instance.sw != null)
            {
                _instance.sw.DestroyHandle();
                _instance.sw = null;
            }

            _instance = null;
        }

        #endregion
    }

    /// <summary>
    /// Used to provide an easy way of accessing the received
    /// arguments when the SecondInstanceLoaded event fires.
    /// </summary>
    class SingletonApplicationEventArgs : EventArgs
    {
        public string[] Args { get; set; }

        public SingletonApplicationEventArgs(string[] args)
        {
            Args = args;
        }
    }
}

Points of Interest

It was interesting and kind of puzzling why this problem wasn't more discussed than it is. My problem was trying to pass an open file argument to the first instance of an application and being unable to find an easy, established way to do this. I would also like to mention that I know this could have been done much more cleanly and that the above code could be much improved upon, and if anybody has any suggestions/comments, I would greatly appreciate them.

History

Posted first version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here