Second Stanza

February 28, 2008

Single Instance Windows Form Application

Filed under: .NET Examples, GUI — Tags: , , — dfbaskin @ 1:10 am

If you have a .NET Windows Form application where you only want a single instance to ever be running, you can do this by using two pieces of functionality, a global mutex and a .NET remoting call. The global mutex is used to determine if another instance of the application is already running. This solves part of the problem, but it is helpful to the end user to show them the initial instance of the application. This is where the .NET remoting call comes in. The new instance uses a .NET remoting call to the initial instance of the application to request that it show itself.

Here’s a simplified example of a single instance Windows Form application:


using System;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.Security.Permissions;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.InteropServices;

namespace SingleInstanceWebForm
{
    public interface IShowApplication
    {
        void ShowApp();
    }

    static class Program
    {
        public const string ApplicationMutexName = "SingleInstanceForm.Mutex";
        public const string IpcChannelName = "SingleInstanceForm.Channel";
        public const string ServiceName = "ShowApp";

        private static SingleInstanceForm formInstance;

        [SecurityPermission( SecurityAction.Demand )]
        private class RemotingObject : MarshalByRefObject, IShowApplication
        {
            void IShowApplication.ShowApp()
            {
                formInstance.ShowApp();
            }
        }

        [SecurityPermission( SecurityAction.Demand )]
        private class RemotingServer : IDisposable
        {
            private IpcChannel ipcServer;

            public RemotingServer()
            {
                this.ipcServer = new IpcChannel( Program.IpcChannelName );
                ChannelServices.RegisterChannel( this.ipcServer, false );

                RemotingConfiguration.ApplicationName = Program.IpcChannelName;
                RemotingConfiguration.RegisterWellKnownServiceType( typeof( RemotingObject ),
                                                                    Program.ServiceName,
                                                                    WellKnownObjectMode.Singleton );
            }

            public void Dispose()
            {
                ChannelServices.UnregisterChannel( this.ipcServer );
            }
        }

        [SecurityPermission( SecurityAction.Demand )]
        private class RemotingClient : IDisposable
        {
            public static string IpcChannelAddress
            {
                get { return String.Format( "ipc://{0}/{1}", IpcChannelName, ServiceName ); }
            }

            private IpcChannel ipcClient;

            public RemotingClient()
            {
                ipcClient = new IpcChannel();
                ChannelServices.RegisterChannel( ipcClient, false );
            }

            public T GetInterface<T>() where T : class
            {
                T objInst = Activator.GetObject( typeof( T ), IpcChannelAddress ) as T;
                if( objInst == null )
                    throw new ArgumentException( String.Format( "Error trying to get instance of {0} object.", typeof( T ).FullName ) );
                return objInst;
            }

            public void Dispose()
            {
                ChannelServices.UnregisterChannel( this.ipcClient );
            }
        }

        [STAThread]
        static void Main()
        {
            try
            {
                bool newMutex;
                using( Mutex appMutex = new Mutex( true, Program.ApplicationMutexName, out newMutex ) )
                {
                    if( newMutex )
                    {
                        using( new RemotingServer() )
                        {
                            Application.EnableVisualStyles();
                            Application.SetCompatibleTextRenderingDefault( false );
                            Application.Run( formInstance = new SingleInstanceForm() );
                        }
                    }
                    else
                    {
                        using( RemotingClient remClient = new RemotingClient() )
                        {
                            IShowApplication showApp = remClient.GetInterface<IShowApplication>();
                            showApp.ShowApp();
                        }
                    }
                }
            }
            catch( Exception ex )
            {
                StringBuilder errTxt = new StringBuilder();
                for( Exception x=ex; x != null; x = x.InnerException )
                    errTxt.AppendFormat( "{0} - {1}\n", x.GetType().FullName, x.Message );
                errTxt.AppendFormat( "Stack Trace:\n{0}", ex.StackTrace );
                MessageBox.Show( errTxt.ToString(), "Application Error", MessageBoxButtons.OK, MessageBoxIcon.Error );
            }
        }
    }

    class SingleInstanceForm : Form, IShowApplication
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct FLASHWINFO
        {
            public UInt32 cbSize;
            public IntPtr hwnd;
            public UInt32 dwFlags;
            public UInt32 uCount;
            public UInt32 dwTimeout;
        }

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

        [System.Flags]
        public enum FLASHW_FLAGS
        {
            FLASHW_ALL = 0x00000003,
            FLASHW_CAPTION = 0x00000001,
            FLASHW_STOP = 0x00000000,
            FLASHW_TIMER = 0x00000004,
            FLASHW_TIMERNOFG = 0x0000000C,
            FLASHW_TRAY = 0x00000002
        }

        private Label txtBox;
        private int initCounter;

        public SingleInstanceForm()
        {
            Text = "Single Instance Form";
            StartPosition = FormStartPosition.CenterScreen;
            Size = new System.Drawing.Size( 400, 300 );

            Controls.Add( this.txtBox = new Label() );
            this.txtBox.Dock = DockStyle.Fill;
            this.txtBox.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
            this.txtBox.Text = "Initialized.";
            this.txtBox.Font = new System.Drawing.Font( "Verdana", 22, System.Drawing.FontStyle.Bold );
        }

        public void ShowApp()
        {
            if( InvokeRequired )
                BeginInvoke( new MethodInvoker( delegate() { ShowApp(); } ) );
            else
            {
                Activate();

                FLASHWINFO flashInfo = new FLASHWINFO();
                flashInfo.cbSize = Convert.ToUInt32( Marshal.SizeOf( flashInfo ) );
                flashInfo.hwnd = Handle;
                flashInfo.dwFlags = Convert.ToUInt32( FLASHW_FLAGS.FLASHW_ALL );
                flashInfo.uCount = 5;
                flashInfo.dwTimeout = 500;
                FlashWindowEx( ref flashInfo );

                this.txtBox.Text = String.Format( "Called {0} time(s).", ++this.initCounter );
            }
        }
    }
}

The FlashWindowEx() function is used to help the user find the window by flashing both the window itself and the task bar button. I’m not sure why this function is not included in the .NET framework, but we can use P/Invoke to access it.

The .NET remoting call uses an IpcChannel, which is a remoting channel that uses the Windows Interprocess Communications system. I couldn’t find documentation about the format of the channel name for an IpcChannel, but other references (such as here) indicate that the name is in the form, “ipc://ChannelName/ServiceName”.

Also note that the MethodInvoker delegate is part of the .NET framework and serves as a general-purpose delegate where no parameters are passed to the method and no return value is expected. Here it is helpful to make sure the Form object is updated on the correct thread when the request to show the application is received.

Blog at WordPress.com.