Question

Utiliser C# et WPF sous .NET (plutôt que Formulaires Windows ou console), quelle est la bonne façon de créer une application qui ne peut être exécutée qu'en tant qu'instance unique ?

Je sais que cela a quelque chose à voir avec une chose mythique appelée mutex, je trouve rarement quelqu'un qui prend la peine de s'arrêter et d'expliquer de quoi il s'agit.

Le code doit également informer l'instance déjà en cours d'exécution que l'utilisateur a essayé d'en démarrer une deuxième, et peut-être également transmettre les arguments de ligne de commande s'il en existe.

Était-ce utile?

La solution

Voici un très bon article concernant la solution Mutex.L'approche décrite dans l'article est avantageuse pour deux raisons.

Premièrement, il ne nécessite pas de dépendance à l’assembly Microsoft.VisualBasic.Si mon projet dépendait déjà de cet assembly, je préconiserais probablement d'utiliser l'approche montré dans une autre réponse.Mais dans l’état actuel des choses, je n’utilise pas l’assembly Microsoft.VisualBasic et je préfère ne pas ajouter de dépendance inutile à mon projet.

Deuxièmement, l'article montre comment mettre l'instance existante de l'application au premier plan lorsque l'utilisateur tente de démarrer une autre instance.C'est une touche très intéressante que les autres solutions Mutex décrites ici ne traitent pas.


MISE À JOUR

Au 01/08/2014, l'article auquel j'ai lié ci-dessus est toujours actif, mais le blog n'a pas été mis à jour depuis un certain temps.Cela me fait craindre qu’il finisse par disparaître, et avec lui, la solution préconisée.Je reproduis ici le contenu de l'article pour la postérité.Les mots appartiennent uniquement au propriétaire du blog à Codage sans bon sens.

Aujourd'hui, je voulais refacter un code qui a interdit à mon application d'exécuter plusieurs instances de lui-même.

Auparavant, j'avais l'habitude Système.Diagnostics.Processus Pour rechercher une instance de mon myapp.exe dans la liste de processus.Bien que cela fonctionne, cela apporte beaucoup de frais généraux et je voulais quelque chose de plus propre.

Sachant que je pouvais utiliser un mutex pour cela (mais je ne l'ai jamais fait auparavant), j'ai décidé de réduire mon code et de simplifier ma vie.

Dans la classe de mon application main, j'ai créé un fichier statique nommé Mutex:

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    ...
}

Avoir un mutex nommé nous permet de piéger la synchronisation sur plusieurs threads et processus, ce qui est juste la magie que je recherche.

Mutex.WaitOne a une surcharge qui spécifie un temps pour nous d'attendre.Puisque nous ne voulons pas réellement synchroniser notre code (plus vérifiez s'il est actuellement utilisé), nous utilisons la surcharge avec deux paramètres: Mutex.WaitOne (délai d'expiration, bool exitContext).Attendez, on renvoie vrai s'il est capable d'entrer, et faux s'il ne l'est pas.Dans ce cas, nous ne voulons pas du tout attendre ;Si notre mutex est utilisé, sautez-le et passez à autre chose, nous passons donc dans Timespan.zero (attendez 0 millisecondes) et définissons l'ExitContext sur true afin que nous puissions quitter le contexte de synchronisation avant d'essayer d'y acquérir une serrure.En utilisant ceci, nous enroulons notre code d'application.

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            MessageBox.Show("only one instance at a time");
        }
    }
}

Donc, si notre application est en cours d'exécution, Waitone reviendra False et nous obtiendrons une boîte de message.

Au lieu d'afficher une boîte de message, j'ai choisi d'utiliser un petit Win32 pour informer mon instance de course que quelqu'un a oublié qu'il était déjà en cours d'exécution (en se faisant en haut de toutes les autres fenêtres).Pour y parvenir, j'ai utilisé PostMessage Pour diffuser un message personnalisé à chaque fenêtre (le message personnalisé a été enregistré avec RegistreFenêtreMessagePar mon application en cours d'exécution, ce qui signifie que mon application sait ce qu'elle est) alors ma deuxième instance sort.L'instance d'application en cours d'exécution recevrait cette notification et la traiterait.Pour ce faire, j'ai dépassé WndProc sous ma forme principale et écouté ma notification personnalisée.Lorsque j'ai reçu cette notification, j'ai défini la propriété la plus haute du formulaire sur TRUE pour le soulever en haut.

Voici ce que j'ai obtenu:

  • Programme.cs
static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            // send our Win32 message to make the currently running instance
            // jump on top of all the other windows
            NativeMethods.PostMessage(
                (IntPtr)NativeMethods.HWND_BROADCAST,
                NativeMethods.WM_SHOWME,
                IntPtr.Zero,
                IntPtr.Zero);
        }
    }
}
  • NativeMethods.cs
// this class just wraps some Win32 stuff that we're going to use
internal class NativeMethods
{
    public const int HWND_BROADCAST = 0xffff;
    public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    [DllImport("user32")]
    public static extern int RegisterWindowMessage(string message);
}
  • Form1.cs (recto partiel)
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    protected override void WndProc(ref Message m)
    {
        if(m.Msg == NativeMethods.WM_SHOWME) {
            ShowMe();
        }
        base.WndProc(ref m);
    }
    private void ShowMe()
    {
        if(WindowState == FormWindowState.Minimized) {
            WindowState = FormWindowState.Normal;
        }
        // get our current "TopMost" value (ours will always be false though)
        bool top = TopMost;
        // make our form jump to the top of everything
        TopMost = true;
        // set it back to whatever it was
        TopMost = top;
    }
}

Autres conseils

Vous pouvez utiliser la classe Mutex, mais vous découvrirez bientôt que vous devrez implémenter le code pour transmettre vous-même les arguments et autres.Eh bien, j'ai appris une astuce lors de la programmation dans WinForms en lisant Le livre de Chris Sell.Cette astuce utilise la logique dont nous disposons déjà dans le framework.Je ne sais pas pour vous, mais lorsque j'apprends des choses que je peux réutiliser dans le framework, c'est généralement la voie que je prends au lieu de réinventer la roue.À moins bien sûr qu’il ne fasse pas tout ce que je veux.

Quand je suis entré dans WPF, j'ai trouvé un moyen d'utiliser ce même code, mais dans une application WPF.Cette solution devrait répondre à vos besoins en fonction de votre question.

Tout d’abord, nous devons créer notre classe d’application.Dans cette classe, nous allons remplacer l'événement OnStartup et créer une méthode appelée Activate, qui sera utilisée plus tard.

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();
    }
}

Deuxièmement, nous devrons créer une classe capable de gérer nos instances.Avant de commencer, nous allons réutiliser du code présent dans l'assembly Microsoft.VisualBasic.Puisque j'utilise C# dans cet exemple, j'ai dû faire une référence à l'assembly.Si vous utilisez VB.NET, vous n'avez rien à faire.La classe que nous allons utiliser est WindowsFormsApplicationBase et en hériter de notre gestionnaire d'instance, puis exploiter les propriétés et les événements pour gérer l'instanciation unique.

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();
    }
}

Fondamentalement, nous utilisons les bits VB pour détecter les instances uniques et les traiter en conséquence.OnStartup sera déclenché lors du chargement de la première instance.OnStartupNextInstance est déclenché lorsque l'application est réexécutée.Comme vous pouvez le voir, je peux accéder à ce qui a été transmis sur la ligne de commande via les arguments de l'événement.J'ai défini la valeur sur un champ d'instance.Vous pouvez analyser la ligne de commande ici ou la transmettre à votre application via le constructeur et l'appel à la méthode Activate.

Troisièmement, il est temps de créer notre EntryPoint.Au lieu de renouveler l'application comme vous le feriez normalement, nous allons profiter de notre SingleInstanceManager.

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

Eh bien, j'espère que vous pourrez tout suivre et pouvoir utiliser cette implémentation et vous l'approprier.

Depuis ici.

Une utilisation courante d'un Mutex inter-processus consiste à garantir que seule une instance d'un programme peut s'exécuter à la fois.Voici comment procéder :

class OneAtATimePlease {

  // Use a name unique to the application (eg include your company URL)
  static Mutex mutex = new Mutex (false, "oreilly.com OneAtATimeDemo");

  static void Main()
  {
    // Wait 5 seconds if contended – in case another instance
    // of the program is in the process of shutting down.
    if (!mutex.WaitOne(TimeSpan.FromSeconds (5), false))
    {
        Console.WriteLine("Another instance of the app is running. Bye!");
        return;
    }

    try
    {    
        Console.WriteLine("Running - press Enter to exit");
        Console.ReadLine();
    }
    finally
    {
        mutex.ReleaseMutex();
    }    
  }    
}

Une bonne fonctionnalité de Mutex est que si l'application se termine sans que ReleaseMutex soit d'abord appelé, le CLR libérera automatiquement le Mutex.

MSDN propose en fait un exemple d'application pour C# et VB pour faire exactement cela : http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx

La technique la plus courante et la plus fiable pour développer une détection à instance unique est d'utiliser l'infrastructure éloignée de Microsoft .NET Framework (System.Remoting).Le Microsoft .NET Framework (version 2.0) comprend un type, WindowsFormsApplicationBase, qui résume la fonctionnalité de télécommande requise.Pour incorporer ce type dans une application WPF, un type doit en dériver et être utilisé comme cale entre la méthode de point de saisie statique de l'application, le principal et le type d'application de l'application WPF.Le calet détecte lorsqu'une application est lancée pour la première fois et lorsque les lancements ultérieurs sont tentés, et les cèdent contrôlent le type d'application WPF pour déterminer comment traiter les lancements.

  • Pour les utilisateurs de C#, respirez profondément et oubliez tout le « Je ne veux pas inclure la DLL VisualBasic ».À cause de ce et quoi Scott Hanselman dit et le fait qu'il s'agit à peu près de la solution la plus propre au problème et qu'elle est conçue par des personnes qui en savent beaucoup plus sur le framework que vous.
  • Du point de vue de la convivialité, le fait est que si votre utilisateur charge une application et qu'elle est déjà ouverte et que vous lui donnez un message d'erreur comme 'Another instance of the app is running. Bye' alors ils ne seront pas des utilisateurs très satisfaits.Vous DEVEZ simplement (dans une application GUI) basculer vers cette application et transmettre les arguments fournis - ou si les paramètres de ligne de commande n'ont aucune signification, vous devez alors afficher l'application qui a peut-être été minimisée.

Le framework prend déjà en charge cela - c'est juste cet idiot nommé la DLL Microsoft.VisualBasic et il n'a pas été intégré Microsoft.ApplicationUtils ou quelque chose comme ça.Surmontez-le - ou ouvrez Reflector.

Conseil:Si vous utilisez cette approche exactement telle quelle et que vous disposez déjà d'un App.xaml avec des ressources, etc.tu voudras regarde ça aussi.

Ce code devrait aller à la méthode principale.Regarder ici pour plus d’informations sur la méthode principale dans WPF.

[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);

private const int SW_SHOWMAXIMIZED = 3;

static void Main() 
{
    Process currentProcess = Process.GetCurrentProcess();
    var runningProcess = (from process in Process.GetProcesses()
                          where
                            process.Id != currentProcess.Id &&
                            process.ProcessName.Equals(
                              currentProcess.ProcessName,
                              StringComparison.Ordinal)
                          select process).FirstOrDefault();
    if (runningProcess != null)
    {
        ShowWindow(runningProcess.MainWindowHandle, SW_SHOWMAXIMIZED);
       return; 
    }
}

Méthode 2

static void Main()
{
    string procName = Process.GetCurrentProcess().ProcessName;
    // get the list of all processes by that name

    Process[] processes=Process.GetProcessesByName(procName);

    if (processes.Length > 1)
    {
        MessageBox.Show(procName + " already running");  
        return;
    } 
    else
    {
        // Application.Run(...);
    }
}

Note : Les méthodes ci-dessus supposent que votre processus/application a un nom unique.Parce qu'il utilise le nom du processus pour rechercher s'il existe des processeurs existants.Ainsi, si votre application porte un nom très courant (c'est-à-dire :Bloc-notes), l'approche ci-dessus ne fonctionnera pas.

Eh bien, j'ai une classe jetable pour cela qui fonctionne facilement pour la plupart des cas d'utilisation :

Utilisez-le comme ceci :

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

        // Initialize program here.
    }
}

C'est ici:

/// <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
}

Un nouveau qui utilise Mutex et IPC, et transmet également tous les arguments de ligne de commande à l'instance en cours d'exécution, est Application à instance unique WPF.

Le code Application à instance unique C# .NET c'est la référence pour la réponse marquée est un bon début.

Cependant, j'ai trouvé qu'il ne gère pas très bien les cas où l'instance qui existe déjà a une boîte de dialogue modale ouverte, que cette boîte de dialogue soit gérée (comme un autre formulaire tel qu'une boîte à propos) ou non gérée (comme le OpenFileDialog même en utilisant la classe .NET standard).Avec le code original, le formulaire principal est activé, mais le formulaire modal reste inactif, ce qui semble étrange, et l'utilisateur doit cliquer dessus pour continuer à utiliser l'application.

J'ai donc créé une classe utilitaire SingleInstance pour gérer tout cela de manière assez automatique pour les applications Winforms et WPF.

Winforms:

1) modifiez la classe Program comme ceci :

static class Program
{
    public static readonly SingleInstance Singleton = new SingleInstance(typeof(Program).FullName);

    [STAThread]
    static void Main(string[] args)
    {
        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        Singleton.RunFirstInstance(() =>
        {
            SingleInstanceMain(args);
        });
    }

    public static void SingleInstanceMain(string[] args)
    {
        // standard code that was in Main now goes here
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

2) modifiez la classe de la fenêtre principale comme ceci :

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    protected override void WndProc(ref Message m)
    {
        // if needed, the singleton will restore this window
        Program.Singleton.OnWndProc(this, m, true);

        // TODO: handle specific messages here if needed
        base.WndProc(ref m);
    }
}

WPF :

1) modifiez la page App comme ceci (et assurez-vous de définir son action de construction sur page pour pouvoir redéfinir la méthode Main) :

public partial class App : Application
{
    public static readonly SingleInstance Singleton = new SingleInstance(typeof(App).FullName);

    [STAThread]
    public static void Main(string[] args)
    {
        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        Singleton.RunFirstInstance(() =>
        {
            SingleInstanceMain(args);
        });
    }

    public static void SingleInstanceMain(string[] args)
    {
        // standard code that was in Main now goes here
        App app = new App();
        app.InitializeComponent();
        app.Run();
    }
}

2) modifiez la classe de la fenêtre principale comme ceci :

public partial class MainWindow : Window
{
    private HwndSource _source;

    public MainWindow()
    {
        InitializeComponent();
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        _source = (HwndSource)PresentationSource.FromVisual(this);
        _source.AddHook(HwndSourceHook);
    }

    protected virtual IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        // if needed, the singleton will restore this window
        App.Singleton.OnWndProc(hwnd, msg, wParam, lParam, true, true);

        // TODO: handle other specific message
        return IntPtr.Zero;
    }

Et voici la classe utilitaire :

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;

namespace SingleInstanceUtilities
{
    public sealed class SingleInstance
    {
        private const int HWND_BROADCAST = 0xFFFF;

        [DllImport("user32.dll")]
        private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        private static extern int RegisterWindowMessage(string message);

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

        public SingleInstance(string uniqueName)
        {
            if (uniqueName == null)
                throw new ArgumentNullException("uniqueName");

            Mutex = new Mutex(true, uniqueName);
            Message = RegisterWindowMessage("WM_" + uniqueName);
        }

        public Mutex Mutex { get; private set; }
        public int Message { get; private set; }

        public void RunFirstInstance(Action action)
        {
            RunFirstInstance(action, IntPtr.Zero, IntPtr.Zero);
        }

        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        public void RunFirstInstance(Action action, IntPtr wParam, IntPtr lParam)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            if (WaitForMutext(wParam, lParam))
            {
                try
                {
                    action();
                }
                finally
                {
                    ReleaseMutex();
                }
            }
        }

        public static void ActivateWindow(IntPtr hwnd)
        {
            if (hwnd == IntPtr.Zero)
                return;

            FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
        }

        public void OnWndProc(IntPtr hwnd, int m, IntPtr wParam, IntPtr lParam, bool restorePlacement, bool activate)
        {
            if (m == Message)
            {
                if (restorePlacement)
                {
                    WindowPlacement placement = WindowPlacement.GetPlacement(hwnd, false);
                    if (placement.IsValid && placement.IsMinimized)
                    {
                        const int SW_SHOWNORMAL = 1;
                        placement.ShowCmd = SW_SHOWNORMAL;
                        placement.SetPlacement(hwnd);
                    }
                }

                if (activate)
                {
                    SetForegroundWindow(hwnd);
                    FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
                }
            }
        }

#if WINFORMS // define this for Winforms apps
        public void OnWndProc(System.Windows.Forms.Form form, int m, IntPtr wParam, IntPtr lParam, bool activate)
        {
            if (form == null)
                throw new ArgumentNullException("form");

            if (m == Message)
            {
                if (activate)
                {
                    if (form.WindowState == System.Windows.Forms.FormWindowState.Minimized)
                    {
                        form.WindowState = System.Windows.Forms.FormWindowState.Normal;
                    }

                    form.Activate();
                    FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(form.Handle));
                }
            }
        }

        public void OnWndProc(System.Windows.Forms.Form form, System.Windows.Forms.Message m, bool activate)
        {
            if (form == null)
                throw new ArgumentNullException("form");

            OnWndProc(form, m.Msg, m.WParam, m.LParam, activate);
        }
#endif

        public void ReleaseMutex()
        {
            Mutex.ReleaseMutex();
        }

        public bool WaitForMutext(bool force, IntPtr wParam, IntPtr lParam)
        {
            bool b = PrivateWaitForMutext(force);
            if (!b)
            {
                PostMessage((IntPtr)HWND_BROADCAST, Message, wParam, lParam);
            }
            return b;
        }

        public bool WaitForMutext(IntPtr wParam, IntPtr lParam)
        {
            return WaitForMutext(false, wParam, lParam);
        }

        private bool PrivateWaitForMutext(bool force)
        {
            if (force)
                return true;

            try
            {
                return Mutex.WaitOne(TimeSpan.Zero, true);
            }
            catch (AbandonedMutexException)
            {
                return true;
            }
        }
    }

    // NOTE: don't add any field or public get/set property, as this must exactly map to Windows' WINDOWPLACEMENT structure
    [StructLayout(LayoutKind.Sequential)]
    public struct WindowPlacement
    {
        public int Length { get; set; }
        public int Flags { get; set; }
        public int ShowCmd { get; set; }
        public int MinPositionX { get; set; }
        public int MinPositionY { get; set; }
        public int MaxPositionX { get; set; }
        public int MaxPositionY { get; set; }
        public int NormalPositionLeft { get; set; }
        public int NormalPositionTop { get; set; }
        public int NormalPositionRight { get; set; }
        public int NormalPositionBottom { get; set; }

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool SetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool GetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);

        private const int SW_SHOWMINIMIZED = 2;

        public bool IsMinimized
        {
            get
            {
                return ShowCmd == SW_SHOWMINIMIZED;
            }
        }

        public bool IsValid
        {
            get
            {
                return Length == Marshal.SizeOf(typeof(WindowPlacement));
            }
        }

        public void SetPlacement(IntPtr windowHandle)
        {
            SetWindowPlacement(windowHandle, ref this);
        }

        public static WindowPlacement GetPlacement(IntPtr windowHandle, bool throwOnError)
        {
            WindowPlacement placement = new WindowPlacement();
            if (windowHandle == IntPtr.Zero)
                return placement;

            placement.Length = Marshal.SizeOf(typeof(WindowPlacement));
            if (!GetWindowPlacement(windowHandle, ref placement))
            {
                if (throwOnError)
                    throw new Win32Exception(Marshal.GetLastWin32Error());

                return new WindowPlacement();
            }
            return placement;
        }
    }

    public static class FormUtilities
    {
        [DllImport("user32.dll")]
        private static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SetActiveWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        private static extern bool IsWindowVisible(IntPtr hWnd);

        [DllImport("kernel32.dll")]
        public static extern int GetCurrentThreadId();

        private delegate bool EnumChildrenCallback(IntPtr hwnd, IntPtr lParam);

        [DllImport("user32.dll")]
        private static extern bool EnumThreadWindows(int dwThreadId, EnumChildrenCallback lpEnumFunc, IntPtr lParam);

        private class ModalWindowUtil
        {
            private const int GW_OWNER = 4;
            private int _maxOwnershipLevel;
            private IntPtr _maxOwnershipHandle;

            private bool EnumChildren(IntPtr hwnd, IntPtr lParam)
            {
                int level = 1;
                if (IsWindowVisible(hwnd) && IsOwned(lParam, hwnd, ref level))
                {
                    if (level > _maxOwnershipLevel)
                    {
                        _maxOwnershipHandle = hwnd;
                        _maxOwnershipLevel = level;
                    }
                }
                return true;
            }

            private static bool IsOwned(IntPtr owner, IntPtr hwnd, ref int level)
            {
                IntPtr o = GetWindow(hwnd, GW_OWNER);
                if (o == IntPtr.Zero)
                    return false;

                if (o == owner)
                    return true;

                level++;
                return IsOwned(owner, o, ref level);
            }

            public static void ActivateWindow(IntPtr hwnd)
            {
                if (hwnd != IntPtr.Zero)
                {
                    SetActiveWindow(hwnd);
                }
            }

            public static IntPtr GetModalWindow(IntPtr owner)
            {
                ModalWindowUtil util = new ModalWindowUtil();
                EnumThreadWindows(GetCurrentThreadId(), util.EnumChildren, owner);
                return util._maxOwnershipHandle; // may be IntPtr.Zero
            }
        }

        public static void ActivateWindow(IntPtr hwnd)
        {
            ModalWindowUtil.ActivateWindow(hwnd);
        }

        public static IntPtr GetModalWindow(IntPtr owner)
        {
            return ModalWindowUtil.GetModalWindow(owner);
        }
    }
}

Voici un exemple qui vous permet d'avoir une seule instance d'une application.Lorsqu'une nouvelle instance est chargée, elle transmet ses arguments à l'instance principale en cours d'exécution.

public partial class App : Application
{
    private static Mutex SingleMutex;
    public static uint MessageId;

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        IntPtr Result;
        IntPtr SendOk;
        Win32.COPYDATASTRUCT CopyData;
        string[] Args;
        IntPtr CopyDataMem;
        bool AllowMultipleInstances = false;

        Args = Environment.GetCommandLineArgs();

        // TODO: Replace {00000000-0000-0000-0000-000000000000} with your application's GUID
        MessageId   = Win32.RegisterWindowMessage("{00000000-0000-0000-0000-000000000000}");
        SingleMutex = new Mutex(false, "AppName");

        if ((AllowMultipleInstances) || (!AllowMultipleInstances && SingleMutex.WaitOne(1, true)))
        {
            new Main();
        }
        else if (Args.Length > 1)
        {
            foreach (Process Proc in Process.GetProcesses())
            {
                SendOk = Win32.SendMessageTimeout(Proc.MainWindowHandle, MessageId, IntPtr.Zero, IntPtr.Zero,
                    Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
                    2000, out Result);

                if (SendOk == IntPtr.Zero)
                    continue;
                if ((uint)Result != MessageId)
                    continue;

                CopyDataMem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Win32.COPYDATASTRUCT)));

                CopyData.dwData = IntPtr.Zero;
                CopyData.cbData = Args[1].Length*2;
                CopyData.lpData = Marshal.StringToHGlobalUni(Args[1]);

                Marshal.StructureToPtr(CopyData, CopyDataMem, false);

                Win32.SendMessageTimeout(Proc.MainWindowHandle, Win32.WM_COPYDATA, IntPtr.Zero, CopyDataMem,
                    Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
                    5000, out Result);

                Marshal.FreeHGlobal(CopyData.lpData);
                Marshal.FreeHGlobal(CopyDataMem);
            }

            Shutdown(0);
        }
    }
}

public partial class Main : Window
{
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        HwndSource Source;

        Source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
        Source.AddHook(new HwndSourceHook(Window_Proc));
    }

    private IntPtr Window_Proc(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam, ref bool Handled)
    {
        Win32.COPYDATASTRUCT CopyData;
        string Path;

        if (Msg == Win32.WM_COPYDATA)
        {
            CopyData = (Win32.COPYDATASTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32.COPYDATASTRUCT));
            Path = Marshal.PtrToStringUni(CopyData.lpData, CopyData.cbData / 2);

            if (WindowState == WindowState.Minimized)
            {
                // Restore window from tray
            }

            // Do whatever we want with information

            Activate();
            Focus();
        }

        if (Msg == App.MessageId)
        {
            Handled = true;
            return new IntPtr(App.MessageId);
        }

        return IntPtr.Zero;
    }
}

public class Win32
{
    public const uint WM_COPYDATA = 0x004A;

    public struct COPYDATASTRUCT
    {
        public IntPtr dwData;
        public int    cbData;
        public IntPtr lpData;
    }

    [Flags]
    public enum SendMessageTimeoutFlags : uint
    {
        SMTO_NORMAL             = 0x0000,
        SMTO_BLOCK              = 0x0001,
        SMTO_ABORTIFHUNG        = 0x0002,
        SMTO_NOTIMEOUTIFNOTHUNG = 0x0008
    }

    [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
    public static extern uint RegisterWindowMessage(string lpString);
    [DllImport("user32.dll")]
    public static extern IntPtr SendMessageTimeout(
        IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam,
        SendMessageTimeoutFlags fuFlags, uint uTimeout, out IntPtr lpdwResult);
}

Juste quelques réflexions :Il existe des cas où exiger qu'une seule instance d'une application n'est pas "boiteuse" comme certains voudraient vous le faire croire.Applications de base de données, etc.sont d'un ordre de grandeur plus difficiles si l'on permet à plusieurs instances de l'application pour un seul utilisateur d'accéder à une base de données (vous savez, tout cela mettant à jour tous les enregistrements ouverts dans plusieurs instances de l'application sur la machine de l'utilisateur, etc.) .Tout d'abord, pour la "collision de noms", n'utilisez pas de nom lisible par l'homme - utilisez plutôt un GUID ou, mieux encore, un GUID + le nom lisible par l'homme.Les risques de collision de noms ont disparu du radar et le Mutex s'en fiche.Comme quelqu'un l'a souligné, une attaque DOS serait nulle, mais si la personne malveillante a pris la peine d'obtenir le nom du mutex et de l'incorporer dans son application, vous êtes de toute façon une cible et devrez faire BEAUCOUP plus pour vous protéger. que de simplement jouer un nom de mutex.Aussi, si l'on utilise la variante de :new Mutex (true, "some GUID plus Name", sur AIsFirstInstance), vous avez déjà votre indicateur indiquant si le Mutex est ou non la première instance.

Autant de réponses à une question apparemment simple.Juste pour faire bouger un peu les choses, voici ma solution à ce problème.

La création d'un Mutex peut être gênante car le JIT-er ne voit que vous l'utilisez pour une petite partie de votre code et souhaite le marquer comme prêt pour le garbage collection.Il veut à peu près vous déjouer en pensant que vous n'allez pas utiliser ce Mutex aussi longtemps.En réalité, vous souhaitez conserver ce Mutex aussi longtemps que votre application est en cours d'exécution.La meilleure façon de dire au garbage collector de laisser votre Mutex tranquille est de lui dire de le garder en vie tout au long des différentes générations de collecte dans les garages.Exemple:

var m = new Mutex(...);
...
GC.KeepAlive(m);

J'ai repris l'idée de cette page : http://www.ai.uga.edu/~mc/SingleInstance.html

Il semble qu'il existe une très bonne façon de gérer cela :

Application à instance unique WPF

Cela fournit une classe que vous pouvez ajouter et qui gère toutes les manipulations de mutex et de messagerie pour simplifier votre implémentation au point où elle est tout simplement triviale.

Le code suivant est ma solution de canaux nommés WCF pour enregistrer une application à instance unique.C'est bien car cela déclenche également un événement lorsqu'une autre instance tente de démarrer et reçoit la ligne de commande de l'autre instance.

Il est destiné à WPF car il utilise le System.Windows.StartupEventHandler classe, mais cela pourrait être facilement modifié.

Ce code nécessite une référence à PresentationFramework, et System.ServiceModel.

Usage:

class Program
{
    static void Main()
    {
        var applicationId = new Guid("b54f7b0d-87f9-4df9-9686-4d8fd76066dc");

        if (SingleInstanceManager.VerifySingleInstance(applicationId))
        {
            SingleInstanceManager.OtherInstanceStarted += OnOtherInstanceStarted;

            // Start the application
        }
    }

    static void OnOtherInstanceStarted(object sender, StartupEventArgs e)
    {
        // Do something in response to another instance starting up.
    }
}

Code source:

/// <summary>
/// A class to use for single-instance applications.
/// </summary>
public static class SingleInstanceManager
{
  /// <summary>
  /// Raised when another instance attempts to start up.
  /// </summary>
  public static event StartupEventHandler OtherInstanceStarted;

  /// <summary>
  /// Checks to see if this instance is the first instance running on this machine.  If it is not, this method will
  /// send the main instance this instance's startup information.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  /// <returns>True if this instance is the main instance.</returns>
  public static bool VerifySingleInstace(Guid guid)
  {
    if (!AttemptPublishService(guid))
    {
      NotifyMainInstance(guid);

      return false;
    }

    return true;
  }

  /// <summary>
  /// Attempts to publish the service.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  /// <returns>True if the service was published successfully.</returns>
  private static bool AttemptPublishService(Guid guid)
  {
    try
    {
      ServiceHost serviceHost = new ServiceHost(typeof(SingleInstance));
      NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
      serviceHost.AddServiceEndpoint(typeof(ISingleInstance), binding, CreateAddress(guid));
      serviceHost.Open();

      return true;
    }
    catch
    {
      return false;
    }
  }

  /// <summary>
  /// Notifies the main instance that this instance is attempting to start up.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  private static void NotifyMainInstance(Guid guid)
  {
    NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
    EndpointAddress remoteAddress = new EndpointAddress(CreateAddress(guid));
    using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(binding, remoteAddress))
    {
      ISingleInstance singleInstance = factory.CreateChannel();
      singleInstance.NotifyMainInstance(Environment.GetCommandLineArgs());
    }
  }

  /// <summary>
  /// Creates an address to publish/contact the service at based on a globally unique identifier.
  /// </summary>
  /// <param name="guid">The identifier for the application.</param>
  /// <returns>The address to publish/contact the service.</returns>
  private static string CreateAddress(Guid guid)
  {
    return string.Format(CultureInfo.CurrentCulture, "net.pipe://localhost/{0}", guid);
  }

  /// <summary>
  /// The interface that describes the single instance service.
  /// </summary>
  [ServiceContract]
  private interface ISingleInstance
  {
    /// <summary>
    /// Notifies the main instance that another instance of the application attempted to start.
    /// </summary>
    /// <param name="args">The other instance's command-line arguments.</param>
    [OperationContract]
    void NotifyMainInstance(string[] args);
  }

  /// <summary>
  /// The implementation of the single instance service interface.
  /// </summary>
  private class SingleInstance : ISingleInstance
  {
    /// <summary>
    /// Notifies the main instance that another instance of the application attempted to start.
    /// </summary>
    /// <param name="args">The other instance's command-line arguments.</param>
    public void NotifyMainInstance(string[] args)
    {
      if (OtherInstanceStarted != null)
      {
        Type type = typeof(StartupEventArgs);
        ConstructorInfo constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
        StartupEventArgs e = (StartupEventArgs)constructor.Invoke(null);
        FieldInfo argsField = type.GetField("_args", BindingFlags.Instance | BindingFlags.NonPublic);
        Debug.Assert(argsField != null);
        argsField.SetValue(e, args);

        OtherInstanceStarted(null, e);
      }
    }
  }
}

Vous ne devez jamais utiliser un mutex nommé pour implémenter une application à instance unique (ou du moins pas pour du code de production).Un code malveillant peut facilement faire un DoS (Déni de service) ton cul...

Voici ce que j'utilise.Il combinait l'énumération des processus pour effectuer la commutation et le mutex pour se protéger des « cliqueurs actifs » :

public partial class App
{
    [DllImport("user32")]
    private static extern int OpenIcon(IntPtr hWnd);

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

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        var p = Process
           .GetProcessesByName(Process.GetCurrentProcess().ProcessName);
            foreach (var t in p.Where(t => t.MainWindowHandle != IntPtr.Zero))
            {
                OpenIcon(t.MainWindowHandle);
                SetForegroundWindow(t.MainWindowHandle);
                Current.Shutdown();
                return;
            }

            // there is a chance the user tries to click on the icon repeatedly
            // and the process cannot be discovered yet
            bool createdNew;
            var mutex = new Mutex(true, "MyAwesomeApp", 
               out createdNew);  // must be a variable, though it is unused - 
            // we just need a bit of time until the process shows up
            if (!createdNew)
            {
                Current.Shutdown();
                return;
            }

            new Bootstrapper().Run();
        }
    }

J'ai trouvé la solution la plus simple, similaire à celle de Dale Ragan, mais légèrement modifiée.Il fait pratiquement tout ce dont vous avez besoin et est basé sur la classe standard Microsoft WindowsFormsApplicationBase.

Tout d'abord, vous créez la classe SingleInstanceController, que vous pouvez utiliser dans toutes les autres applications à instance unique qui utilisent Windows Forms :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.VisualBasic.ApplicationServices;


namespace SingleInstanceController_NET
{
    public class SingleInstanceController
    : WindowsFormsApplicationBase
    {
        public delegate Form CreateMainForm();
        public delegate void StartNextInstanceDelegate(Form mainWindow);
        CreateMainForm formCreation;
        StartNextInstanceDelegate onStartNextInstance;
        public SingleInstanceController(CreateMainForm formCreation, StartNextInstanceDelegate onStartNextInstance)
        {
            // Set whether the application is single instance
            this.formCreation = formCreation;
            this.onStartNextInstance = onStartNextInstance;
            this.IsSingleInstance = true;

            this.StartupNextInstance += new StartupNextInstanceEventHandler(this_StartupNextInstance);                      
        }

        void this_StartupNextInstance(object sender, StartupNextInstanceEventArgs e)
        {
            if (onStartNextInstance != null)
            {
                onStartNextInstance(this.MainForm); // This code will be executed when the user tries to start the running program again,
                                                    // for example, by clicking on the exe file.
            }                                       // This code can determine how to re-activate the existing main window of the running application.
        }

        protected override void OnCreateMainForm()
        {
            // Instantiate your main application form
            this.MainForm = formCreation();
        }

        public void Run()
        {
            string[] commandLine = new string[0];
            base.Run(commandLine);
        }
    }
}

Ensuite, vous pouvez l'utiliser dans votre programme comme suit :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using SingleInstanceController_NET;

namespace SingleInstance
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static Form CreateForm()
        {
            return new Form1(); // Form1 is used for the main window.
        }

        static void OnStartNextInstance(Form mainWindow) // When the user tries to restart the application again,
                                                         // the main window is activated again.
        {
            mainWindow.WindowState = FormWindowState.Maximized;
        }
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);            
            SingleInstanceController controller = new SingleInstanceController(CreateForm, OnStartNextInstance);
            controller.Run();         
        }
    }
}

Le programme et la solution SingleInstanceController_NET ​​doivent faire référence à Microsoft.VisualBasic .Si vous souhaitez simplement réactiver l'application en cours d'exécution comme une fenêtre normale lorsque l'utilisateur tente de redémarrer le programme en cours d'exécution, le deuxième paramètre de SingleInstanceController peut être nul.Dans l'exemple donné, la fenêtre est maximisée.

Regardez le code suivant.C'est une solution simple et efficace pour empêcher plusieurs instances d'une application WPF.

private void Application_Startup(object sender, StartupEventArgs e)
{
    Process thisProc = Process.GetCurrentProcess();
    if (Process.GetProcessesByName(thisProc.ProcessName).Length > 1)
    {
        MessageBox.Show("Application running");
        Application.Current.Shutdown();
        return;
    }

    var wLogin = new LoginWindow();

    if (wLogin.ShowDialog() == true)
    {
        var wMain = new Main();
        wMain.WindowState = WindowState.Maximized;
        wMain.Show();
    }
    else
    {
        Application.Current.Shutdown();
    }
}

Je n'utilise pas Mutex cependant, réponse simple :

System.Diagnostics;    
...
string thisprocessname = Process.GetCurrentProcess().ProcessName;

if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                return;

Mettez-le à l'intérieur du Program.Main().
Exemple:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;

namespace Sample
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            //simple add Diagnostics namespace, and these 3 lines below 
            string thisprocessname = Process.GetCurrentProcess().ProcessName;
            if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                return;

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Sample());
        }
    }
}

Vous pouvez ajouter MessageBox.Show au if-instruction et mettez "Application déjà en cours d'exécution".
Cela pourrait être utile à quelqu'un.

Mise à jour 2017-01-25. Après avoir essayé quelques choses, j'ai décidé d'utiliser VisualBasic.dll, c'est plus facile et fonctionne mieux (du moins pour moi).Je laisse ma réponse précédente juste comme référence...

Juste à titre de référence, voici comment j'ai fait sans passer d'arguments (ce que je ne trouve aucune raison de le faire...Je veux dire une seule application avec des arguments qui doivent être transmis d'une instance à une autre).Si une association de fichiers est requise, une application doit (selon les attentes standard des utilisateurs) être instanciée pour chaque document.Si vous devez transmettre des arguments à une application existante, je pense que j'utiliserais vb dll.

Ne pas transmettre d'arguments (juste une application à instance unique), je préfère ne pas enregistrer de nouveau message Windows et ne pas remplacer la boucle de message telle que définie dans la solution Matt Davis.Bien que ce ne soit pas grave d'ajouter une DLL VisualBasic, je préfère ne pas ajouter de nouvelle référence uniquement pour créer une application à instance unique.De plus, je préfère instancier une nouvelle classe avec Main au lieu d'appeler Shutdown depuis App.Startup pour garantir la sortie dès que possible.

En espérant que ça plaira à quelqu'un...ou ça inspirera un peu :-)

La classe de démarrage du projet doit être définie sur « SingleInstanceApp ».

public class SingleInstanceApp
{
    [STAThread]
    public static void Main(string[] args)
    {
        Mutex _mutexSingleInstance = new Mutex(true, "MonitorMeSingleInstance");

        if (_mutexSingleInstance.WaitOne(TimeSpan.Zero, true))
        {
            try
            {
                var app = new App();
                app.InitializeComponent();
                app.Run();

            }
            finally
            {
                _mutexSingleInstance.ReleaseMutex();
                _mutexSingleInstance.Close();
            }
        }
        else
        {
            MessageBox.Show("One instance is already running.");

            var processes = Process.GetProcessesByName(Assembly.GetEntryAssembly().GetName().Name);
            {
                if (processes.Length > 1)
                {
                    foreach (var process in processes)
                    {
                        if (process.Id != Process.GetCurrentProcess().Id)
                        {
                            WindowHelper.SetForegroundWindow(process.MainWindowHandle);
                        }
                    }
                }
            }
        }
    }
}

Aide-fenêtre :

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;

namespace HQ.Util.Unmanaged
{
    public class WindowHelper
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetForegroundWindow(IntPtr hWnd);

Utilisez la solution mutex :

using System;
using System.Windows.Forms;
using System.Threading;

namespace OneAndOnlyOne
{
static class Program
{
    static String _mutexID = " // generate guid"
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        Boolean _isNotRunning;
        using (Mutex _mutex = new Mutex(true, _mutexID, out _isNotRunning))
        {
            if (_isNotRunning)
            {
                Application.Run(new Form1());
            }
            else
            {
                MessageBox.Show("An instance is already running.");
                return;
            }
        }
    }
}
}

Voici une solution légère que j'utilise qui permet à l'application de mettre une fenêtre déjà existante au premier plan sans recourir à des messages Windows personnalisés ni à une recherche aveugle des noms de processus.

[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;
}

Modifier:Vous pouvez également stocker et initialiser mutex et createNew de manière statique, mais vous devrez explicitement supprimer/libérer le mutex une fois que vous en aurez terminé.Personnellement, je préfère garder le mutex local car il sera automatiquement supprimé même si l'application se ferme sans jamais atteindre la fin de Main.

Vous pouvez également utiliser le Exécution CodeFluent qui est un ensemble d'outils gratuit.Il fournit un Seule instance classe pour implémenter une application à instance unique.

Voici la même chose implémentée via Event.

public enum ApplicationSingleInstanceMode
{
    CurrentUserSession,
    AllSessionsOfCurrentUser,
    Pc
}

public class ApplicationSingleInstancePerUser: IDisposable
{
    private readonly EventWaitHandle _event;

    /// <summary>
    /// Shows if the current instance of ghost is the first
    /// </summary>
    public bool FirstInstance { get; private set; }

    /// <summary>
    /// Initializes 
    /// </summary>
    /// <param name="applicationName">The application name</param>
    /// <param name="mode">The single mode</param>
    public ApplicationSingleInstancePerUser(string applicationName, ApplicationSingleInstanceMode mode = ApplicationSingleInstanceMode.CurrentUserSession)
    {
        string name;
        if (mode == ApplicationSingleInstanceMode.CurrentUserSession)
            name = $"Local\\{applicationName}";
        else if (mode == ApplicationSingleInstanceMode.AllSessionsOfCurrentUser)
            name = $"Global\\{applicationName}{Environment.UserDomainName}";
        else
            name = $"Global\\{applicationName}";

        try
        {
            bool created;
            _event = new EventWaitHandle(false, EventResetMode.ManualReset, name, out created);
            FirstInstance = created;
        }
        catch
        {
        }
    }

    public void Dispose()
    {
        _event.Dispose();
    }
}

Les approches basées sur les mutex nommés ne sont pas multiplateformes car les mutex nommés ne sont pas globaux dans Mono.Les approches basées sur l'énumération des processus n'ont aucune synchronisation et peuvent entraîner un comportement incorrect (par ex.plusieurs processus démarrés en même temps peuvent tous se terminer automatiquement en fonction du timing).Les approches basées sur un système de fenêtrage ne sont pas souhaitables dans une application console.Cette solution, construite sur la réponse de Divin, résout tous ces problèmes :

using System;
using System.IO;

namespace TestCs
{
    public class Program
    {
        // The app id must be unique. Generate a new guid for your application. 
        public static string AppId = "01234567-89ab-cdef-0123-456789abcdef";

        // The stream is stored globally to ensure that it won't be disposed before the application terminates.
        public static FileStream UniqueInstanceStream;

        public static int Main(string[] args)
        {
            EnsureUniqueInstance();

            // Your code here.

            return 0;
        }

        private static void EnsureUniqueInstance()
        {
            // Note: If you want the check to be per-user, use Environment.SpecialFolder.ApplicationData instead.
            string lockDir = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
                "UniqueInstanceApps");
            string lockPath = Path.Combine(lockDir, $"{AppId}.unique");

            Directory.CreateDirectory(lockDir);

            try
            {
                // Create the file with exclusive write access. If this fails, then another process is executing.
                UniqueInstanceStream = File.Open(lockPath, FileMode.Create, FileAccess.Write, FileShare.None);

                // Although only the line above should be sufficient, when debugging with a vshost on Visual Studio
                // (that acts as a proxy), the IO exception isn't passed to the application before a Write is executed.
                UniqueInstanceStream.Write(new byte[] { 0 }, 0, 1);
                UniqueInstanceStream.Flush();
            }
            catch
            {
                throw new Exception("Another instance of the application is already running.");
            }
        }
    }
}

je ne trouve pas de solution courte ici donc j'espère que quelqu'un aimera ça :

MISE À JOUR 2018-09-20

(D'ailleurs.mets le code dans ton "Programme.cs")

    using System.Diagnostics;

    static void Main()
    {
        Process ThisProcess = Process.GetCurrentProcess();
        Process[] AllProcesses = Process.GetProcessesByName(ThisProcess.ProcessName);
        if (AllProcesses.Length > 1)
        {
            //Don't put a MessageBox in here because the user could spam this MessageBox.
            return;
        }

// Code facultatif.Si vous ne voulez pas que quelqu'un s'exécute, vous êtes ".exe" avec un nom différent :

        string exeName = AppDomain.CurrentDomain.FriendlyName;
        if (exeName != "the name of you're executable.exe") // If you try that in debug mode, don't forget that u don't use ur normal .exe. Debug uses the .vshost.exe.
        {// You can add here a MessageBox if you want. To point users that the name got changed and maybe what the name should be or something like that^^ 
            MessageBox.Show("The executable name should be \"the name of you're executable.exe\"", 
            "Wrong executable name", MessageBoxButtons.OK, MessageBoxIcon.Error);
            return;
        }

        //Following Code is default code:
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());
    }

J'ai ajouté une méthode sendMessage à la classe NativeMethods.

Apparemment, la méthode postmessage ne fonctionne pas, si l'application n'est pas affichée dans la barre des tâches, mais l'utilisation de la méthode sendmessage résout ce problème.

class NativeMethods
{
    public const int HWND_BROADCAST = 0xffff;
    public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
    [DllImport("user32")]
    public static extern int RegisterWindowMessage(string message);
}

Habituellement, chaque fois que nous exécutons un .exe, il crée à chaque fois un processus Windows distinct avec son propre espace d'adressage, ses propres ressources, etc.Mais nous ne voulons pas de ce critère car cela nous empêcherait de créer un processus unique.Des applications à instance unique peuvent être créées à l'aide du Mutex en C# qui est discuté dans cet article

De plus, si nous voulons mettre l'application au premier plan, nous pouvons le faire en utilisant

 [DllImport("user32")]
 static extern IntPtr SetForegroundWindow(IntPtr hWnd);

Normalement, c'est le code que j'utilise pour les instances uniques Formulaires Windows applications:

[STAThread]
public static void Main()
{
    String assemblyName = Assembly.GetExecutingAssembly().GetName().Name;

    using (Mutex mutex = new Mutex(false, assemblyName))
    {
        if (!mutex.WaitOne(0, false))
        {
            Boolean shownProcess = false;
            Process currentProcess = Process.GetCurrentProcess();

            foreach (Process process in Process.GetProcessesByName(currentProcess.ProcessName))
            {
                if (!process.Id.Equals(currentProcess.Id) && process.MainModule.FileName.Equals(currentProcess.MainModule.FileName) && !process.MainWindowHandle.Equals(IntPtr.Zero))
                {
                    IntPtr windowHandle = process.MainWindowHandle;

                    if (NativeMethods.IsIconic(windowHandle))
                        NativeMethods.ShowWindow(windowHandle, ShowWindowCommand.Restore);

                    NativeMethods.SetForegroundWindow(windowHandle);

                    shownProcess = true;
                }
            }

            if (!shownProcess)
                MessageBox.Show(String.Format(CultureInfo.CurrentCulture, "An instance of {0} is already running!", assemblyName), assemblyName, MessageBoxButtons.OK, MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
        }
        else
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form());
        }
    }
}

Où se trouvent les composants natifs :

[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean IsIconic([In] IntPtr windowHandle);

[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean SetForegroundWindow([In] IntPtr windowHandle);

[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean ShowWindow([In] IntPtr windowHandle, [In] ShowWindowCommand command);

public enum ShowWindowCommand : int
{
    Hide                   = 0x0,
    ShowNormal             = 0x1,
    ShowMinimized          = 0x2,
    ShowMaximized          = 0x3,
    ShowNormalNotActive    = 0x4,
    Minimize               = 0x6,
    ShowMinimizedNotActive = 0x7,
    ShowCurrentNotActive   = 0x8,
    Restore                = 0x9,
    ShowDefault            = 0xA,
    ForceMinimize          = 0xB
}

Voici une solution :

Protected Overrides Sub OnStartup(e As StartupEventArgs)
    Const appName As String = "TestApp"
    Dim createdNew As Boolean
    _mutex = New Mutex(True, appName, createdNew)
    If Not createdNew Then
        'app is already running! Exiting the application
        MessageBox.Show("Application is already running.")
        Application.Current.Shutdown()
    End If
    MyBase.OnStartup(e)
End Sub

C'est ainsi que j'ai fini par m'occuper de ce problème.Notez que le code de débogage est toujours là pour les tests.Ce code se trouve dans OnStartup dans le fichier App.xaml.cs.(WPF)

        // Process already running ? 
        if (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length > 1)
        {

            // Show your error message
            MessageBox.Show("xxx is already running.  \r\n\r\nIf the original process is hung up you may need to restart your computer, or kill the current xxx process using the task manager.", "xxx is already running!", MessageBoxButton.OK, MessageBoxImage.Exclamation);

            // This process 
            Process currentProcess = Process.GetCurrentProcess();

            // Get all processes running on the local computer.
            Process[] localAll = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName);

            // ID of this process... 
            int temp = currentProcess.Id;
            MessageBox.Show("This Process ID:  " + temp.ToString());

            for (int i = 0; i < localAll.Length; i++)
            {
                // Find the other process 
                if (localAll[i].Id != currentProcess.Id)
                {
                    MessageBox.Show("Original Process ID (Switching to):  " + localAll[i].Id.ToString());

                    // Switch to it... 
                    SetForegroundWindow(localAll[i].MainWindowHandle);

                }
            }

            Application.Current.Shutdown();

        }

Cela peut avoir des problèmes que je n'ai pas encore détectés.Si j'en rencontre, je mettrai à jour ma réponse.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top