Click here to Skip to main content
15,892,927 members
Articles / Programming Languages / C#
Article

Enforcing Single Instance of a Hidden-Window Application

Rate me:
Please Sign up or sign in to vote.
4.44/5 (14 votes)
17 Nov 2006CPOL3 min read 79.2K   803   35   20
Yet another article on single instance, but with a twist.

Introduction

Single instance application. There may not be a topic on which there are more good quality articles, so why another? All the methods I could find rely on the main window of the first instance being visible, which won't be the case if you have an application that hides its main window, such as a system tray application. Not only does this method work in the case of a hidden window, but compares favorably for the general case too.

Background

It turns out that detecting whether your application is already running is not very hard. There are several Win32 synchronization objects that will help (this project uses events), or you can enumerate windows and search their titles (this won't work if the window is hidden though). On the other hand, it is difficult to get the first instance of the application to display its window. Especially if the window is hidden, any method using messages or manipulating the window handle won't work.

Here are some other articles you might find helpful. I'm kind of partial to this one, which also has some code for displaying an animation as the application minimizes to the sys tray area of the task bar (which, by the way, is apparently unnecessary in XP): A Single Instance Application which Minimizes to the System Tray when Closed.

Here's another recent one that has the added advantage of having no unmanaged code (unlike my example): Single Instance Application, Passing Command Line Arguments.

Using the code

The method is simple, but the implementation a little less so. This is how it's done: the application attempts to open a named event. If the event exists, then the application knows it is the second instance. In that case, it signals the event and exits. If the named event doesn't exist, then the application creates it and starts a thread which waits for the event to be signaled. If the event becomes signaled, it uses P/Invoke to call a delegate on the GUI thread. The delegate shows the window, then calls a Win32 function to bring itself to the foreground.

The code that creates and checks the event is in a class called Event. The app's Main will create an Event object and use it to check whether another instance is already running. Here's what the Main looks like:

C#
static void Main() 
{
    // this class provides single instance feature
    evnt = new Event();
    
    // already exists means another instance is running
    if ( evnt.EventAlreadyExists() )
    {
        // signal the other instance to come to foreground
        evnt.SignalEvent();
    }
    else
    {
        // otherwise start normally
        SingleInstanceForm f = new SingleInstanceForm();
        // important: access the Handle so .net will create it
        IntPtr dummy = f.Handle;
        f.eventSignalled = 
           new EventSignalledHandler( f.evnt_EventSignalled );
        evnt.SetObject( f );

        Application.Run();
    }

}

Notice that I don't call Application.Run() with the form reference, which allows this application to start without showing a window. While this method doesn't need a visible window, P/Invoke does need a valid window handle. Accessing the form's handle forces .NET to create it for us.

Here's what the Event constructor looks like:

C#
public Event()
{
    eventHandle = OpenEvent( EVENT_MODIFY_STATE | 
                  SYNCHRONIZE, false, EVENT_NAME );
    if ( eventHandle == IntPtr.Zero )
    {
        eventHandle = CreateEvent( IntPtr.Zero, true, 
                      false, EVENT_NAME );
        if ( eventHandle != IntPtr.Zero )
        {
            Thread thread = 
               new Thread( new ThreadStart( WaitForSignal ) );
            thread.Start();
        }
    }
    else
    {
        eventAlreadyExists = true;
    }

}

OpenEvent will return a valid handle only if another instance has created it. So, it's very important that the handle gets closed, which is why I implement the IDisposable interface. If the handle were left open, your app would run the first time, but would not be able to start again until after a reboot or logout.

Win32 Synchronization object names can be defined on a per session or a per machine basis. This project prepends "Local\" to the name, which means it is only unique for the currently logged in user.

Points of interest

I learned about implementing IDisposable from this article: Implementing IDisposable and the Dispose Pattern Properly.

One of the keys to this project is calling a method on a GUI thread from a worker thread, and in .NET, this is handled by P/Invoke. There are many good articles on CodeProject about P/Invoke, but I also want to point out a great reference site: http://www.pinvoke.net/.

I look forward to hearing comments and better or more clear ways of doing what I've shown here. One thing I don't like is that the Event class is tied to the form class definition. One way to fix that is to create a new class that inherits from Form and that defines the necessary delegate.

On Windows 2000, SetForegroundWindow() doesn't always work as expected.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
I live near San Diego, CA. I wouldn't recommend it unless you can handle a severe lack of cold and snow.

Comments and Discussions

 
GeneralProcess not being killed Pin
Gonzalo Cao5-Aug-08 0:54
Gonzalo Cao5-Aug-08 0:54 
GeneralRe: Process not being killed Pin
J. David Reynolds30-Oct-08 11:44
J. David Reynolds30-Oct-08 11:44 
GeneralRe: Process not being killed Pin
drdre200530-Oct-08 12:10
drdre200530-Oct-08 12:10 
GeneralMade some slight mods for re-usability [modified] Pin
dadda_mac21-Feb-08 5:27
dadda_mac21-Feb-08 5:27 
Generalpassing command line arguments Pin
MarioMARTIN31-Oct-07 1:52
MarioMARTIN31-Oct-07 1:52 
Hi all!

I wrote another verion that is able to pass command line arguments using named native Windows pipes.

A version that uses WCF and 'NetNamedPipeBinding' would also be possible.


using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32.SafeHandles;
using System.IO;
using System.ComponentModel;

namespace Common
{
  /// <summary>
  /// Class that is used to check whether the current running application is
  /// unique (the first instance) or whether there is already another instance
  /// running.
  /// 
  /// If there is already another instance running the new instance passes its
  /// commandline arguments to the priviously started application instance.
  /// </summary>
  public class SingleInstanceManager : IDisposable
  {
    #region delegates
    /// <summary>
    /// Gets called when this is the first instance and another instance tries
    /// to start
    /// </summary>
    /// <param name="args">Command line arguments of the second instance</param>
    public delegate void ShowApplicationCallback( String args );
    private ShowApplicationCallback m_ShowAppCallback;


    /// <summary>
    /// Gets called when this is NOT the first instance
    /// </summary>
    public delegate void TerminateApplicationCallback();
    private TerminateApplicationCallback m_TerminateAppCallback;
    #endregion


    #region contained definitions
    /// <summary>
    /// Wrapper for native Windows functions, flags and constants
    /// </summary>
    private class NativeMethods
    {
      #region flags and enums
      [Flags]
      internal enum CreateNamedPipeFlags : uint
      {
        PIPE_TYPE_BYTE = 0,
        PIPE_TYPE_MESSAGE = 0x00000004,

        PIPE_READMODE_BYTE = 0,
        PIPE_READMODE_MESSAGE = 0x00000002,

        PIPE_WAIT = 0,

        PIPE_ACCESS_INBOUND = 0x00000001,
        PIPE_ACCESS_OUTBOUND = 0x00000002,
        PIPE_ACCESS_DUPLEX = 0x00000003,
      }

      internal enum CreateNamedPipeConstants : uint
      {
        NUMBER_OF_PIPE_INST = 1,
        OUT_BUFFER_SIZE = 256,
        IN_BUFFER_SIZE = 256,
      }

      [Flags]
      internal enum CreateFileFlags : uint
      {
        GENERIC_READ = 0x80000000,
        GENERIC_WRITE = 0x40000000,
      }

      internal enum CreateFileConstants : uint
      {
        OPEN_EXISTING = 3,
      }
      #endregion

      #region native Windows functions
      [DllImport( "Kernel32.dll" )]
      public static extern SafeFileHandle CreateNamedPipe( string lpName,
                                       [MarshalAs( UnmanagedType.I4 )]
                                       CreateNamedPipeFlags dwOpenMode,
                                       [MarshalAs( UnmanagedType.I4 )]
                                       CreateNamedPipeFlags dwPipeMode,
                                       [MarshalAs( UnmanagedType.I4 )]
                                       CreateNamedPipeConstants nMaxInstances,
                                       [MarshalAs( UnmanagedType.I4 )]
                                       CreateNamedPipeConstants nOutBufferSize,
                                       [MarshalAs( UnmanagedType.I4 )]
                                       CreateNamedPipeConstants nInBufferSize,
                                       [MarshalAs( UnmanagedType.I4 )]
                                       CreateNamedPipeConstants nDefaultTimeOut,
                                       IntPtr lpSecurityAttributes );

      [DllImport( "Kernel32.dll", SetLastError = true )]
      [return: MarshalAs( UnmanagedType.I4 )]
      public extern static Int32 ConnectNamedPipe( SafeFileHandle namedPipe,
                                                   IntPtr overlapped );

      [DllImport( "Kernel32.dll", SetLastError = true )]
      public extern static SafeFileHandle CreateFile( string name,
                                       [MarshalAs( UnmanagedType.I4 )]
                                       CreateFileFlags desiredAccess,
                                       [MarshalAs( UnmanagedType.I4 )]
                                       CreateFileFlags shareMode,
                                       IntPtr securityAttributes,
                                       [MarshalAs( UnmanagedType.I4 )]
                                       CreateFileConstants creationDisposition,
                                       [MarshalAs( UnmanagedType.I4 )]
                                       UInt32 flagsAndAttributes,
                                       IntPtr templateFile );

      [DllImport( "Kernel32.dll" )]
      [return: MarshalAs( UnmanagedType.Bool )]
      public static extern bool DisconnectNamedPipe( SafeFileHandle hNamedPipe );
      #endregion
    }
    #endregion


    #region constructors
    /// <summary>
    /// Constructor that initializes the SingleInstanceManager
    /// </summary>
    /// <param name="applicationName">Unique string that identifies your 
    /// application</param>
    /// <param name="showCallback">Callback that gets executed when another 
    /// instance of your application tries to start</param>
    /// <param name="terminateCallback">Callback that gets executed when another 
    /// instance of your application already got started</param>
    public SingleInstanceManager( String applicationName,
                                  ShowApplicationCallback showCallback,
                                  TerminateApplicationCallback terminateCallback )
    {
      m_ShowAppCallback = showCallback;
      m_TerminateAppCallback = terminateCallback;
      m_MutexName = applicationName;
      m_Arguments = Environment.CommandLine; //ToDo: Remove Process name
    }


    /// <summary>
    /// Hidden default constructor
    /// </summary>
    private SingleInstanceManager()
    { }
    #endregion


    #region public member functions
    /// <summary>
    /// Call this function after you instantiated the SingleInstanceManager
    /// to check whether this is the first instance or not.
    /// 
    /// Throws: SingleInstanceManagerException
    /// </summary>
    public void CheckUniqueness()
    {
      bool isMutexNew = false;

      try
      {
        //Try to create our Mutex
        m_Mutex = new Mutex( true, m_MutexName, out isMutexNew );

        if ( isMutexNew == true )
        {
          ClaimUniqueness();
        }
        else
        {
          NotifyUniqueInstance();
        }
      }
      catch ( Exception e )
      {
        throw new SingleInstanceManagerException( 
                     "SingleInstanceManager could not CheckUniqueness. " +
                     "Details :" + e.Message );
      }
    }
    #endregion


    #region private member functions
    /// <summary>
    /// Gets called when this instance is the first (the unique) instance and
    /// waits for commandline arguments of other instances
    /// </summary>
    private void ClaimUniqueness()
    {      
        //Open a pipe for the commandline arguments of other instances
        CreatePipe();

        //Wait for other instances
        m_Thread = new Thread( new ThreadStart( WaitForSignal ) );
        m_Thread.Start();
    }


    /// <summary>
    /// Passes the commandline arguments to the unique instance and calls
    /// ShowApplicationCallback afterwards
    /// </summary>
    private void NotifyUniqueInstance()
    { 
        m_Pipe = NativeMethods.CreateFile( @"\\.\pipe\" + m_MutexName,
                                     NativeMethods.CreateFileFlags.GENERIC_WRITE,
                                     0,
                                     IntPtr.Zero,
                                     NativeMethods.CreateFileConstants.OPEN_EXISTING,
                                     0,
                                     IntPtr.Zero );

        if ( m_Pipe.IsInvalid )
          throw new Win32Exception( Marshal.GetLastWin32Error() );

        //Pass arguments into the pipe
        using ( TextWriter textWriter =
          new StreamWriter( new FileStream( m_Pipe, FileAccess.Write ) ) )
        {
          textWriter.WriteLine( m_Arguments );
          textWriter.Flush();
        }

      //call the termination callback
      m_TerminateAppCallback();
    }


    /// <summary>
    /// Creates a pipe that is used to receive commandline arguments
    /// </summary>
    private void CreatePipe()
    {
        m_Pipe = NativeMethods.CreateNamedPipe( @"\\.\pipe\" + m_MutexName,
                                    NativeMethods.CreateNamedPipeFlags.PIPE_ACCESS_DUPLEX,
                                    NativeMethods.CreateNamedPipeFlags.PIPE_TYPE_BYTE |
                                      NativeMethods.CreateNamedPipeFlags.PIPE_READMODE_BYTE |
                                      NativeMethods.CreateNamedPipeFlags.PIPE_WAIT,
                                    NativeMethods.CreateNamedPipeConstants.NUMBER_OF_PIPE_INST,
                                    NativeMethods.CreateNamedPipeConstants.OUT_BUFFER_SIZE,
                                    NativeMethods.CreateNamedPipeConstants.IN_BUFFER_SIZE,
                                    0,
                                    IntPtr.Zero );

        if ( m_Pipe.IsInvalid )
          throw new Win32Exception( Marshal.GetLastWin32Error() );

        m_TextReader = new StreamReader( new FileStream( m_Pipe, FileAccess.Read ) );
    }


    /// <summary>
    /// Waits for commandline arguments
    /// 
    /// Throws: SingleInstanceManagerException
    /// </summary>
    private void WaitForSignal()
    {
      while ( true )
      {
        try
        {
          //Connect to the pipe
          NativeMethods.ConnectNamedPipe( m_Pipe, IntPtr.Zero );

          m_Arguments = m_TextReader.ReadLine();

          m_ShowAppCallback( m_Arguments );

          //Disconnect from pipe
          NativeMethods.DisconnectNamedPipe( m_Pipe );

        }
        catch ( Exception e )
        {
          throw new SingleInstanceManagerException(
                      "SingleInstanceManager encountered a problem. " +
                      "Details :" + e.Message );
        }
      }
    }

    #region IDisposable Members

    protected virtual void Dispose( bool disposeManagedResources )
    {
      if ( !this.disposed )
      {
        
        if ( m_Pipe != null )
          m_Pipe.Dispose();

        if ( m_TextReader != null )
          m_TextReader.Dispose();

        if ( m_Thread != null )
          m_Thread.Abort();

        disposed = true;
      }
    }


    public void Dispose()
    {
      Dispose( true );
      GC.SuppressFinalize( this );
    }


    // destructor
    ~SingleInstanceManager()
    {
      Dispose( false );
    }
    #endregion

    #endregion

    #region member variables

    private bool disposed = false;

    private Mutex m_Mutex;
    private SafeFileHandle m_Pipe;

    private TextReader m_TextReader;

    private String m_MutexName;
    private String m_Arguments;

    private Thread m_Thread;

    #endregion
  }

  
  [global::System.Serializable]
  public class SingleInstanceManagerException : Exception
  {

    public SingleInstanceManagerException() { }
    public SingleInstanceManagerException( string message ) : base( message ) { }
    public SingleInstanceManagerException( string message, Exception inner ) : base( message, inner ) { }
    protected SingleInstanceManagerException(
    System.Runtime.Serialization.SerializationInfo info,
    System.Runtime.Serialization.StreamingContext context )
      : base( info, context ) { }
  }
}






Cheers,

Mario M.


Dear CodeProject member: Please don't forget to show me how clever you are by rating also this message as "crap/spam/trash" instead of writing a meaningful response!


QuestionHow would you do this in WPF? Pin
JamesHurst22-Aug-07 11:59
JamesHurst22-Aug-07 11:59 
AnswerRe: How would you do this in WPF? Pin
drdre200522-Aug-07 12:20
drdre200522-Aug-07 12:20 
GeneralRe: How would you do this in WPF? Pin
JamesHurst22-Aug-07 17:16
JamesHurst22-Aug-07 17:16 
AnswerRe: How would you do this in WPF? Pin
NormDroid27-Sep-07 23:30
professionalNormDroid27-Sep-07 23:30 
GeneralGreat! Pin
MarioMARTIN13-Aug-07 4:10
MarioMARTIN13-Aug-07 4:10 
GeneralRe: Great! Pin
drdre200519-Aug-07 5:34
drdre200519-Aug-07 5:34 
GeneralRe: Great! - Hidden? Pin
JamesHurst22-Aug-07 17:06
JamesHurst22-Aug-07 17:06 
GeneralRe: Great! - Hidden? Pin
MarioMARTIN22-Aug-07 19:55
MarioMARTIN22-Aug-07 19:55 
GeneralRe: Great! - Hidden? Pin
NormDroid27-Sep-07 23:21
professionalNormDroid27-Sep-07 23:21 
GeneralSome Wpf alternatives Pin
FocusedWolf3-Apr-09 5:46
FocusedWolf3-Apr-09 5:46 
GeneralSimpler solution using a Mutex Pin
Joe McRay18-Nov-06 8:39
Joe McRay18-Nov-06 8:39 
GeneralRe: Simpler solution using a Mutex Pin
Steve Hansen20-Nov-06 1:32
Steve Hansen20-Nov-06 1:32 
GeneralRe: Simpler solution using a Mutex Pin
drdre200520-Nov-06 4:45
drdre200520-Nov-06 4:45 
GeneralRe: Simpler solution using a Mutex Pin
SuperDave00729-Nov-06 4:55
SuperDave00729-Nov-06 4:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.