What is the correct way to create a single-instance WPF application?

前端 未结 30 3180
耶瑟儿~
耶瑟儿~ 2020-11-21 05:14

Using C# and WPF under .NET (rather than Windows Forms or console), what is the correct way to create an application that can only be run as a single instance?

I kno

相关标签:
30条回答
  • 2020-11-21 05:48

    Just some thoughts: There are cases when requiring that only one instance of an application is not "lame" as some would have you believe. Database apps, etc. are an order of magnitude more difficult if one allows multiple instances of the app for a single user to access a database (you know, all that updating all the records that are open in multiple instances of the app on the users machine, etc.). First, for the "name collision thing, don't use a human readable name - use a GUID instead or, even better a GUID + the human readable name. Chances of name collision just dropped off the radar and the Mutex doesn't care. As someone pointed out, a DOS attack would suck, but if the malicious person has gone to the trouble of getting the mutex name and incorporating it into their app, you are pretty much a target anyway and will have to do MUCH more to protect yourself than just fiddle a mutex name. Also, if one uses the variant of: new Mutex(true, "some GUID plus Name", out AIsFirstInstance), you already have your indicator as to whether or not the Mutex is the first instance.

    0 讨论(0)
  • 2020-11-21 05:48

    [I have provided sample code for console and wpf applications below.]

    You only have to check the value of the createdNew variable (example below!), after you create the named Mutex instance.

    The boolean createdNew will return false:

    if the Mutex instance named "YourApplicationNameHere" was already created on the system somewhere

    The boolean createdNew will return true:

    if this is the first Mutex named "YourApplicationNameHere" on the system.


    Console application - Example:

    static Mutex m = null;
    
    static void Main(string[] args)
    {
        const string mutexName = "YourApplicationNameHere";
        bool createdNew = false;
    
        try
        {
            // Initializes a new instance of the Mutex class with a Boolean value that indicates 
            // whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex, 
            // and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.
    
            using (m = new Mutex(true, mutexName, out createdNew))
            {
                if (!createdNew)
                {
                    Console.WriteLine("instance is alreday running... shutting down !!!");
                    Console.Read();
                    return; // Exit the application
                }
    
                // Run your windows forms app here
                Console.WriteLine("Single instance app is running!");
                Console.ReadLine();
            }
    
    
        }
        catch (Exception ex)
        {
    
            Console.WriteLine(ex.Message);
            Console.ReadLine();
        }
    }
    

    WPF-Example:

    public partial class App : Application
    {
    static Mutex m = null;
    
    protected override void OnStartup(StartupEventArgs e)
    {
    
        const string mutexName = "YourApplicationNameHere";
        bool createdNew = false;
    
        try
        {
            // Initializes a new instance of the Mutex class with a Boolean value that indicates 
            // whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex, 
            // and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.
    
            m = new Mutex(true, mutexName, out createdNew);
    
            if (!createdNew)
            {
                Current.Shutdown(); // Exit the application
            }
    
        }
        catch (Exception)
        {
            throw;
        }
    
        base.OnStartup(e);
    }
    
    
    protected override void OnExit(ExitEventArgs e)
    {
        if (m != null)
        {
            m.Dispose();
        }
        base.OnExit(e);
    }
    }
    
    0 讨论(0)
  • 2020-11-21 05:52

    Here's a lightweight solution I use which allows the application to bring an already existing window to the foreground without resorting to custom windows messages or blindly searching process names.

    [DllImport("user32.dll")]
    static extern bool SetForegroundWindow(IntPtr hWnd);
    
    static readonly string guid = "<Application Guid>";
    
    static void Main()
    {
        Mutex mutex = null;
        if (!CreateMutex(out mutex))
            return;
    
        // Application startup code.
    
        Environment.SetEnvironmentVariable(guid, null, EnvironmentVariableTarget.User);
    }
    
    static bool CreateMutex(out Mutex mutex)
    {
        bool createdNew = false;
        mutex = new Mutex(false, guid, out createdNew);
    
        if (createdNew)
        {
            Process process = Process.GetCurrentProcess();
            string value = process.Id.ToString();
    
            Environment.SetEnvironmentVariable(guid, value, EnvironmentVariableTarget.User);
        }
        else
        {
            string value = Environment.GetEnvironmentVariable(guid, EnvironmentVariableTarget.User);
            Process process = null;
            int processId = -1;
    
            if (int.TryParse(value, out processId))
                process = Process.GetProcessById(processId);
    
            if (process == null || !SetForegroundWindow(process.MainWindowHandle))
                MessageBox.Show("Unable to start application. An instance of this application is already running.");
        }
    
        return createdNew;
    }
    

    Edit: You can also store and initialize mutex and createdNew statically, but you'll need to explicitly dispose/release the mutex once you're done with it. Personally, I prefer keeping the mutex local as it will be automatically disposed of even if the application closes without ever reaching the end of Main.

    0 讨论(0)
  • 2020-11-21 05:53

    Please check the proposed solution from here that uses a semaphore to determine if an existing instance is already running, works for a WPF application and can pass arguments from second instance to the first already running instance by using a TcpListener and a TcpClient:

    It works also for .NET Core, not only for .NET Framework.

    0 讨论(0)
  • 2020-11-21 05:54

    You could use the Mutex class, but you will soon find out that you will need to implement the code to pass the arguments and such yourself. Well, I learned a trick when programming in WinForms when I read Chris Sell's book. This trick uses logic that is already available to us in the framework. I don't know about you, but when I learn about stuff I can reuse in the framework, that is usually the route I take instead of reinventing the wheel. Unless of course it doesn't do everything I want.

    When I got into WPF, I came up with a way to use that same code, but in a WPF application. This solution should meet your needs based off your question.

    First, we need to create our application class. In this class we are going override the OnStartup event and create a method called Activate, which will be used later.

    public class SingleInstanceApplication : System.Windows.Application
    {
        protected override void OnStartup(System.Windows.StartupEventArgs e)
        {
            // Call the OnStartup event on our base class
            base.OnStartup(e);
    
            // Create our MainWindow and show it
            MainWindow window = new MainWindow();
            window.Show();
        }
    
        public void Activate()
        {
            // Reactivate the main window
            MainWindow.Activate();
        }
    }
    

    Second, we will need to create a class that can manage our instances. Before we go through that, we are actually going to reuse some code that is in the Microsoft.VisualBasic assembly. Since, I am using C# in this example, I had to make a reference to the assembly. If you are using VB.NET, you don't have to do anything. The class we are going to use is WindowsFormsApplicationBase and inherit our instance manager off of it and then leverage properties and events to handle the single instancing.

    public class SingleInstanceManager : Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
    {
        private SingleInstanceApplication _application;
        private System.Collections.ObjectModel.ReadOnlyCollection<string> _commandLine;
    
        public SingleInstanceManager()
        {
            IsSingleInstance = true;
        }
    
        protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs eventArgs)
        {
            // First time _application is launched
            _commandLine = eventArgs.CommandLine;
            _application = new SingleInstanceApplication();
            _application.Run();
            return false;
        }
    
        protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
        {
            // Subsequent launches
            base.OnStartupNextInstance(eventArgs);
            _commandLine = eventArgs.CommandLine;
            _application.Activate();
        }
    }
    

    Basically, we are using the VB bits to detect single instance's and process accordingly. OnStartup will be fired when the first instance loads. OnStartupNextInstance is fired when the application is re-run again. As you can see, I can get to what was passed on the command line through the event arguments. I set the value to an instance field. You could parse the command line here, or you could pass it to your application through the constructor and the call to the Activate method.

    Third, it's time to create our EntryPoint. Instead of newing up the application like you would normally do, we are going to take advantage of our SingleInstanceManager.

    public class EntryPoint
    {
        [STAThread]
        public static void Main(string[] args)
        {
            SingleInstanceManager manager = new SingleInstanceManager();
            manager.Run(args);
        }
    }
    

    Well, I hope you are able to follow everything and be able use this implementation and make it your own.

    0 讨论(0)
  • 2020-11-21 05:54

    Well, I have a disposable Class for this that works easily for most use cases:

    Use it like this:

    static void Main()
    {
        using (SingleInstanceMutex sim = new SingleInstanceMutex())
        {
            if (sim.IsOtherInstanceRunning)
            {
                Application.Exit();
            }
    
            // Initialize program here.
        }
    }
    

    Here it is:

    /// <summary>
    /// Represents a <see cref="SingleInstanceMutex"/> class.
    /// </summary>
    public partial class SingleInstanceMutex : IDisposable
    {
        #region Fields
    
        /// <summary>
        /// Indicator whether another instance of this application is running or not.
        /// </summary>
        private bool isNoOtherInstanceRunning;
    
        /// <summary>
        /// The <see cref="Mutex"/> used to ask for other instances of this application.
        /// </summary>
        private Mutex singleInstanceMutex = null;
    
        /// <summary>
        /// An indicator whether this object is beeing actively disposed or not.
        /// </summary>
        private bool disposed;
    
        #endregion
    
        #region Constructor
    
        /// <summary>
        /// Initializes a new instance of the <see cref="SingleInstanceMutex"/> class.
        /// </summary>
        public SingleInstanceMutex()
        {
            this.singleInstanceMutex = new Mutex(true, Assembly.GetCallingAssembly().FullName, out this.isNoOtherInstanceRunning);
        }
    
        #endregion
    
        #region Properties
    
        /// <summary>
        /// Gets an indicator whether another instance of the application is running or not.
        /// </summary>
        public bool IsOtherInstanceRunning
        {
            get
            {
                return !this.isNoOtherInstanceRunning;
            }
        }
    
        #endregion
    
        #region Methods
    
        /// <summary>
        /// Closes the <see cref="SingleInstanceMutex"/>.
        /// </summary>
        public void Close()
        {
            this.ThrowIfDisposed();
            this.singleInstanceMutex.Close();
        }
    
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
    
        private void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                /* Release unmanaged ressources */
    
                if (disposing)
                {
                    /* Release managed ressources */
                    this.Close();
                }
    
                this.disposed = true;
            }
        }
    
        /// <summary>
        /// Throws an exception if something is tried to be done with an already disposed object.
        /// </summary>
        /// <remarks>
        /// All public methods of the class must first call this.
        /// </remarks>
        public void ThrowIfDisposed()
        {
            if (this.disposed)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
        }
    
        #endregion
    }
    
    0 讨论(0)
提交回复
热议问题