Question Quelle est la manière correcte de créer une application à instance unique?


En utilisant C # et WPF sous .NET (plutôt que Windows Forms ou console), quelle est la bonne façon de créer une application qui ne peut être exécutée que comme une seule instance?

Je sais que cela a quelque chose à voir avec quelque chose de mythique appelé mutex, rarement je peux trouver quelqu'un qui s'inquiète de s'arrêter et d'expliquer ce que l'un d'eux est.

Le code doit également informer l'instance déjà en cours que l'utilisateur a essayé de démarrer un second, et peut-être également passer tous les arguments de ligne de commande s'il en existe un.


549
2017-08-21 00:33


origine


Réponses:


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

Tout d'abord, il ne nécessite pas de dépendance à l'assembly Microsoft.VisualBasic. Si mon projet dépendait déjà de cet assemblage, je recommanderais probablement d'utiliser l'approche indiquée dans la réponse acceptée. Mais en l'état, je n'utilise pas l'assembly Microsoft.VisualBasic et je préfère ne pas ajouter une dépendance inutile à mon projet.

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


METTRE À JOUR

À compter du 01/08/2014, l'article auquel j'ai lié est toujours actif, mais le blog n'a pas été mis à jour depuis un moment. Cela m'inquiète que finalement il pourrait disparaître, et avec lui, la solution préconisée. Je reproduis le contenu de l'article ici pour la postérité. Les mots appartiennent uniquement au propriétaire du blog à Codage sans danger.

Aujourd'hui, je voulais refactoriser du code qui interdisait mon application   d'exécuter plusieurs instances de lui-même.

Auparavant j'avais l'usage System.Diagnostics.Process chercher un   instance de mon myapp.exe dans la liste des processus. Alors que cela fonctionne, il   apporte beaucoup de frais généraux, et je voulais quelque chose de plus propre.

Sachant que je pourrais utiliser un mutex pour cela (mais ne l'ayant jamais fait)   avant), j'ai entrepris de réduire mon code et de simplifier ma vie.

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

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

Avoir un mutex nommé nous permet d'empiler la synchronisation à travers   plusieurs threads et processus qui est juste la magie que je cherche   pour.

Mutex.WaitOne a une surcharge qui spécifie une quantité de temps pour nous   attendre. Puisque nous ne voulons pas synchroniser notre code   (plus juste vérifier s'il est actuellement utilisé) nous utilisons la surcharge avec   deux paramètres: Mutex.WaitOne (Timeout timeout, bool exitContext).   Attendre un retourne true s'il est capable d'entrer, et false si ce n'est pas le cas.   Dans ce cas, nous ne voulons pas attendre du tout; Si notre mutex est en cours   l'utiliser, l'ignorer et passer à autre chose, donc nous passons dans TimeSpan.Zero (attendez 0   millisecondes), et définissez exitContext sur true pour que nous puissions quitter le   contexte de synchronisation avant d'essayer d'acquérir un verrou dessus. En utilisant   ceci, nous enveloppons notre code Application.Run à l'intérieur de quelque chose comme ceci:

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 retournera false, et nous aurons un   messagerie.

Au lieu de montrer une boîte de message, j'ai choisi d'utiliser un peu Win32 à   avertir mon instance en cours que quelqu'un a oublié qu'il était déjà   courir (en se portant au sommet de toutes les autres fenêtres). À   atteindre cela, j'ai utilisé PostMessage diffuser un message personnalisé à chaque   fenêtre (le message personnalisé a été enregistré avec RegisterWindowMessage   par mon application en cours d'exécution, ce qui signifie que seule mon application sait ce que   c'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, je   passer outre WndProc dans ma forme principale et écouté pour ma coutume   notification. Lorsque j'ai reçu cette notification, j'ai défini le formulaire   La propriété TopMost est définie sur true pour l'afficher.

Voici ce que j'ai fini par:

  • Program.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 (partie avant partielle)
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;
    }
}

441
2018-02-07 01:18



Vous pouvez utiliser la classe Mutex, mais vous découvrirez bientôt que vous devrez implémenter le code pour passer les arguments et vous-même. Eh bien, j'ai appris un truc lors de la programmation dans WinForms quand je lis Le livre de Chris Sell. Cette astuce utilise une logique qui est déjà disponible pour nous dans le cadre. Je ne sais pas pour vous, mais quand j'apprends des choses que je peux réutiliser dans le cadre, c'est généralement la route que je prends au lieu de réinventer la roue. À moins bien sûr que ça 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.

Premièrement, 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 Activer, 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 aurons besoin de créer une classe capable de gérer nos instances. Avant de passer à travers cela, nous allons réellement réutiliser du code qui se trouve dans l'assembly Microsoft.VisualBasic. Comme j'utilise C # dans cet exemple, j'ai dû faire référence à l'assemblage. Si vous utilisez VB.NET, vous n'avez rien à faire. La classe que nous allons utiliser est WindowsFormsApplicationBase et hérite de notre gestionnaire d'instance, puis exploite les propriétés et les événements pour gérer l'instance 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 traiter en conséquence. OnStartup sera déclenché lors du chargement de la première instance. OnStartupNextInstance est déclenché lorsque l'application est à nouveau exécutée. Comme vous pouvez le voir, je peux obtenir ce qui a été passé sur la ligne de commande à travers les arguments de l'événement. Je définis la valeur sur un champ d'instance. Vous pouvez analyser la ligne de commande ici, ou vous pouvez le passer à 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 mettre en place 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 êtes capable de tout suivre et de pouvoir utiliser cette mise en œuvre et de le faire vôtre.


92
2017-08-21 04:17



De ici.

Une utilisation courante pour un processus croisé Mutex est de s'assurer que seule une instance d'un programme peut s'exécuter à la fois. Voici comment c'est fait:

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 ne soit appelé, le CLR libérera automatiquement le Mutex.


75
2017-08-21 00:47



MSDN a réellement un exemple d'application pour C # et VB pour faire exactement ceci: 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 instance unique   la détection consiste à utiliser le Microsoft .NET   Infrastructure à distance de cadre   (System.Remoting). Le Microsoft .NET   Framework (version 2.0) inclut un   type, WindowsFormsApplicationBase,   qui encapsule le nécessaire   fonctionnalité d'accès distant. Incorporer   ce type dans une application WPF, un   le type doit en dériver, et être   utilisé comme une cale entre l'application   méthode du point d'entrée statique, Principal, et   l'application de l'application WPF   type. La cale détecte quand un   l'application est lancée, et   lorsque les lancements suivants sont   tenté, et donne le contrôle du WPF   Type d'application pour déterminer comment   traiter les lancements.

  • Pour les personnes C #, il suffit de prendre une profonde respiration et d'oublier l'ensemble «Je ne veux pas inclure DLL VisualBasic». En raison de ce et quoi Scott Hanselman dit et le fait que c'est à peu près la solution la plus propre au problème et est conçu par des gens qui en savent beaucoup plus sur le cadre que vous.
  • Du point de vue de l'ergonomie, 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 vont pas être un utilisateur très heureux. Vous devez simplement (dans une application GUI) basculer vers cette application et transmettre les arguments fournis - ou si les paramètres de la ligne de commande n'ont aucune signification, vous devez faire apparaître l'application qui peut avoir été minimisée.

Le framework a déjà un support pour cela - c'est juste que certains idiot nommés DLL Microsoft.VisualBasic et il n'a pas été mis en Microsoft.ApplicationUtils ou quelque chose comme ça. Surmontez-le - ou ouvrez Reflector.

Astuce: Si vous utilisez cette approche exactement comme vous l'avez fait, et que vous avez déjà un App.xaml avec des ressources, etc. Jetez un oeil à cela aussi.


48
2017-09-27 21:48



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

Remarque : Les méthodes ci-dessus supposent que votre processus / application a un nom unique. Parce qu'il utilise le nom du processus pour trouver des processeurs existants. Donc, si votre application a un nom très commun (ex: Notepad), l'approche ci-dessus ne fonctionnera pas.


20
2017-08-24 22:48



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
}

15
2018-02-19 10:39



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


12
2018-05-28 19:58



Voici un exemple qui vous permet d'avoir une seule instance d'une application. Lorsque de nouvelles instances sont chargées, elles transmettent leurs 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);
}

9
2018-05-07 08:17