Quelle est la bonne façon de créer une application WPF à instance unique?

657

En utilisant C # et WPF sous .NET (plutôt que Windows Forms ou la 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 mythique appelé mutex, rarement je peux trouver quelqu'un qui prend la peine de s'arrêter et d'expliquer ce que c'est.

Le code doit également informer l'instance déjà en cours d'exécution que l'utilisateur a tenté d'en démarrer une seconde, et peut-être également transmettre les arguments de ligne de commande, le cas échéant.

Nidonocu
la source
14
Le CLR ne libère-t-il pas automatiquement les mutex non libérés quand l'application se termine quand même?
Cocowalla
1
@Cocowalla: le finaliseur doit supprimer les mutex non gérés à moins qu'il ne puisse pas savoir si le mutex a été créé par l'application gérée ou attaché à une application existante.
Ignacio Soler Garcia
Avoir une seule instance de votre application est raisonnable. Mais passer des arguments à une application déjà existante me semble un peu idiot. Je ne vois aucune raison de le faire. Si vous associez une application à une extension de fichier, vous devez ouvrir autant d'applications que l'utilisateur souhaite ouvrir de documents. C'est le comportement standard auquel tous les utilisateurs s'attendent.
Eric Ouellet
9
@Cocowalla Le CLR ne gère pas les ressources natives. Cependant, si un processus se termine, tous les descripteurs sont libérés par le système (le système d'exploitation, pas le CLR).
IInspectable
1
Je préfère la réponse de @huseyint. Il utilise la propre classe 'SingleInstance.cs' de Microsoft, vous n'avez donc pas à vous soucier des Mutex et des IntPtrs. En outre, aucune dépendance à VisualBasic (yuk). Voir codereview.stackexchange.com/questions/20871/… pour plus ...
Heliac

Réponses:

537

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 sur l'assembly Microsoft.VisualBasic. Si mon projet dépendait déjà de cet assemblage, je recommanderais probablement d'utiliser l'approche indiquée dans une autre réponse . 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 n'abordent pas.


MISE À JOUR

Au 8/1/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’elle finisse par disparaître et, avec elle, 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 de Sanity Free Coding .

Aujourd'hui, je voulais refactoriser un code qui interdisait à mon application d'exécuter plusieurs instances d'elle-même.

Auparavant, j'avais utilisé System.Diagnostics.Process pour rechercher une instance de mon myapp.exe dans la liste des processus. Bien que cela fonctionne, cela entraîne beaucoup de frais généraux et je voulais quelque chose de plus propre.

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

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

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 sur plusieurs threads et processus, ce qui est juste la magie que je recherche.

Mutex.WaitOne a une surcharge qui spécifie un délai d'attente. Étant donné que nous ne voulons pas réellement synchroniser notre code (il suffit de vérifier s'il est actuellement utilisé), nous utilisons la surcharge avec deux paramètres: Mutex.WaitOne (timeout timepan, bool exitContext) . Attendez que l'un retourne vrai s'il peut entrer et faux s'il ne l'est pas. Dans ce cas, nous ne voulons pas attendre du tout; Si notre mutex est utilisé, sautez-le et continuez, nous passons donc TimeSpan.Zero (attendez 0 millisecondes) et définissons exitContext sur true afin de pouvoir quitter le contexte de synchronisation avant d'essayer d'acquérir un verrou dessus. En utilisant cela, nous enveloppons notre code Application.Run dans 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 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 en cours d'exécution que quelqu'un a oublié qu'il était déjà en cours d'exécution (en se plaçant en haut de toutes les autres fenêtres). Pour y parvenir, j'ai utilisé PostMessage pour diffuser un message personnalisé dans chaque fenêtre (le message personnalisé a été enregistré auprès de RegisterWindowMessage par mon application en cours d'exécution, ce qui signifie que seule mon application sait de quoi il s'agit), puis ma deuxième instance se ferme. L'instance d'application en cours d'exécution recevrait cette notification et la traiterait. Pour ce faire, j'ai remplacé WndProc dans mon formulaire principal et écouté ma notification personnalisée. Lorsque j'ai reçu cette notification, j'ai défini la propriété TopMost du formulaire sur true pour l'afficher en haut.

Voici ce que j'ai fini avec:

  • 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 (face 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;
    }
}
Matt Davis
la source
5
Sur la base que cette réponse utilise moins de code et moins de bibliothèques et fournit la fonctionnalité de relance au sommet, je vais en faire la nouvelle réponse acceptée. Si quelqu'un connaît une manière plus correcte d'amener le formulaire au sommet à l'aide d'API, n'hésitez pas à l'ajouter.
Nidonocu
11
@BlueRaja, vous démarrez la première instance d'application. Lorsque vous démarrez la deuxième instance d'application, il détecte qu'une autre instance est déjà en cours d'exécution et se prépare à l'arrêt. Avant cela, il envoie un message natif "SHOWME" à la première instance, ce qui amène la première instance au sommet. Les événements dans .NET ne permettent pas la communication entre processus, c'est pourquoi le message natif est utilisé.
Matt Davis
7
Existe-t-il un moyen de passer les lignes de commande de l'autre instance, peut-être?
gyurisc
22
@Nam, le Mutexconstructeur nécessite simplement une chaîne, vous pouvez donc fournir n'importe quel nom de chaîne que vous voulez, par exemple, "This Is My Mutex". Étant donné qu'un «Mutex» est un objet système disponible pour d'autres processus, vous souhaitez généralement que le nom soit unique afin qu'il ne se heurte pas aux autres noms de «Mutex» sur le même système. Dans l'article, la chaîne d'apparence cryptique est un «Guid». Vous pouvez générer cela par programmation en appelant System.Guid.NewGuid(). Dans le cas de l'article, l'utilisateur l'a probablement généré via Visual Studio comme indiqué ici: msdn.microsoft.com/en-us/library/ms241442(VS.80).aspx
Matt Davis
6
L'approche mutex suppose-t-elle que le même utilisateur tente de redémarrer l'application? Certes, mettre "l'instance existante de l'application au premier plan" n'a pas de sens après un "changement d'utilisateur"
dumbledad
107

Vous pouvez utiliser la classe Mutex, mais vous découvrirez bientôt que vous devrez implémenter le code pour passer les arguments et autres vous-même. Eh bien, j'ai appris une astuce lors de la programmation dans WinForms en lisant le livre de Chris Sell . Cette astuce utilise une logique qui est déjà à notre disposition 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 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 passer par là, nous allons réellement réutiliser du code qui se trouve dans l'assembly Microsoft.VisualBasic. Depuis, 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éritera notre gestionnaire d'instances, puis tirera parti des propriétés et des é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 s'est passé sur la ligne de commande via les arguments d'é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.

Dale Ragan
la source
9
Je m'en tiendrai à la solution mutex car elle n'a rien à voir avec les formulaires.
Steven Sudit
1
Je l'ai utilisé parce que j'avais des problèmes avec d'autres approches, mais je suis assez sûr qu'il utilise la télécommande sous le capot. Mon application a eu deux problèmes liés - certains clients disent qu'elle essaie de téléphoner à la maison même s'ils l'ont dit de ne pas le faire. Quand ils regardent plus attentivement, la connexion est à localhost. Pourtant, ils ne le savent pas au départ. De plus, je ne peux pas utiliser la télécommande à des fins différentes (je pense?) Car elle est déjà utilisée pour cela. Lorsque j'ai essayé l'approche mutex, j'ai pu utiliser à nouveau la télécommande.
Richard Watson
4
Pardonnez-moi, mais à moins que je manque quelque chose, vous avez évité d'écrire 3 lignes de code et à la place vous avez réutilisé le framework juste pour écrire du code assez lourd pour le faire. Alors, où sont les économies?
greenoldman
2
il est possible de le faire sous forme de winforms?
Jack
1
Si vous n'appelez pas InitializeComponent () sur l'instance d'application, vous ne pourrez pas résoudre les ressources ... _application = new SingleInstanceApplication (); _application.InitializeComponent (); _application.Run ();
Nick
84

D' ici .

Une utilisation courante d'un Mutex inter-processus est de 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 caractéristique de Mutex est que si l'application se termine sans que ReleaseMutex soit appelé pour la première fois, le CLR libérera automatiquement Mutex.

Jason Saldo
la source
5
Je dois dire que j'aime cette réponse beaucoup plus que celle acceptée simplement parce qu'elle ne dépend pas de WinForms. Personnellement, la majeure partie de mon développement est passée à WPF et je ne veux pas avoir à extraire les bibliothèques WinForm pour quelque chose comme ça.
Switters
5
Bien sûr, pour être une réponse complète, vous devez également décrire le passage des arguments à l'autre instance :)
Simon Buchan
@Jason, bien, merci! Mais je préfère ne pas passer de temps mort. C'est tellement subjectif et dépend de tant de variables. Si jamais vous souhaitez activer une autre application pour démarrer, libérez simplement votre mutex plus rapidement .. par exemple dès que l'utilisateur confirme la fermeture
Eric Ouellet
@EricOuellet: À peu près tous les programmes qui ont des onglets le font - Photoshop, Sublime Text, Chrome .... Si vous avez une bonne raison d'avoir un processus "maître" (par exemple, vous avez une base de données pour les paramètres) vous pourriez je veux qu'il montre l'interface utilisateur comme s'il s'agissait d'un nouveau processus.
Simon Buchan
@Simon, tu as raison. Je me questionne juste sur une chose très ancienne ... MDI vs SDI (Multi document interface vs Single document interface). Lorsque vous parlez d'onglets, vous vous référez à MDI. En 1998, un livre de Microsoft suggère d'éliminer toutes les applications MDI. Microsoft a changé Word, Excel ... en SDI, ce qui, je pense, est plus simple et meilleur. Je comprends que Chrome et d'autres (maintenant IE) souhaitent revenir à MDI. Personnellement (sur la base de rien / sentiments personnels), il est préférable d'ouvrir une nouvelle application lorsque l'assoc de fichier est sélectionné. Mais je comprends mieux la question posée maintenant. Merci !
Eric Ouellet
58

MSDN a 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 d'instance unique consiste à utiliser l'infrastructure d'accès distant Microsoft .NET Framework (System.Remoting). Microsoft .NET Framework (version 2.0) inclut un type, WindowsFormsApplicationBase, qui encapsule la fonctionnalité d'accès distant requise. Pour incorporer ce type dans une application WPF, un type doit en dériver et être utilisé comme shim entre la méthode de point d'entrée statique de l'application, Main, et le type d'application de l'application WPF. Le module d'interface détecte quand une application est lancée pour la première fois, et quand des lancements ultérieurs sont tentés, et les rendements contrôlent le type d'application WPF pour déterminer comment traiter les lancements.

  • Pour C #, les gens prennent juste une profonde inspiration et oublient le tout "Je ne veux pas inclure de DLL VisualBasic". Pour cette raison et ce que dit Scott Hanselman et le fait que c'est à peu près la solution la plus propre au problème et qu'il est conçu par des gens qui en savent beaucoup plus sur le cadre que vous.
  • Du point de vue de l'utilisabilité, 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'ça, il ne sera pas un utilisateur très heureux. Vous DEVEZ simplement (dans une application GUI) basculer vers cette application et passer les arguments fournis - ou si les paramètres de ligne de commande n'ont pas de sens, vous devez faire apparaître l'application qui peut avoir été minimisée.

Le cadre a déjà un support pour cela - c'est juste qu'un idiot a nommé la DLL Microsoft.VisualBasicet il n'a pas été inséré Microsoft.ApplicationUtilsou quelque chose comme ça. Dépassez-le - ou ouvrez Reflector.

Astuce: Si vous utilisez cette approche exactement telle quelle, et que vous avez déjà un App.xaml avec des ressources, etc., vous voudrez également y jeter un œil .

Simon_Weaver
la source
Merci d'avoir inclus le lien "Jetez un œil à ceci aussi". C'est exactement ce dont j'avais besoin. Soit dit en passant, la solution n ° 3 de votre lien est la meilleure.
Eternal21
Je suis également un partisan de la délégation au cadre et aux bibliothèques spécialement conçues lorsque cela est possible.
Eniola
23

Ce code doit aller à la méthode principale. Regardez 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 courant (par exemple: Bloc-notes), l'approche ci-dessus ne fonctionnera pas.

CharithJ
la source
1
De plus, cela ne fonctionnera pas s'il existe un autre programme en cours d'exécution sur votre ordinateur avec le même nom. ProcessNamerenvoie le nom du fichier exécutable moins le exe. Si vous créez une application appelée "Bloc-notes" et que le bloc-notes Windows est en cours d'exécution, il le détectera comme votre application en cours d'exécution.
2015
1
Merci pour cette réponse. J'ai trouvé tellement de questions similaires et les réponses ont toujours été si élaborées et / ou déroutantes que je les ai trouvées inutiles. Celui-ci (méthode # 1) est simple, clair et surtout il m'a réellement aidé à faire fonctionner mon code.
ElDoRado1239
20

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
}
Oliver Friedrich
la source
1
celui-ci était assez facile à travailler. Il ne fermerait pas la deuxième application tant que je n'aurais pas modifié Application.Exit (); à un simple retour; mais à part ça c'est super. Bien que j'avoue que je vais regarder de plus près la solution précédente car elle utilise une interface. blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/…
hal9000
15

WPF Single Instance Application est un nouveau qui utilise des éléments Mutex et IPC et qui transmet également tous les arguments de ligne de commande à l'instance en cours d'exécution .

huseyint
la source
J'utilise cela avec beaucoup de succès. Si vous incorporez NamedPipes à cela, vous pouvez également passer des arguments de ligne de commande à l'application d'origine. La classe «SingleInstance.cs» a été écrite par Microsoft. J'ai ajouté un autre lien vers une version plus lisible du blog d'Arik Poznanski sur CodeProject.
Heliac
1
Le lien est maintenant rompu.
Mike Lowery
11

Le code C # .NET Single Instance Application qui 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 de dialogue), ou non gérée (comme le OpenFileDialog même lors de l'utilisation de la classe .NET standard). Avec le code d'origine, le formulaire principal est activé, mais le 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 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 de l'application comme ceci (et assurez-vous de définir son action de génération 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 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 d'utilité:

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);
        }
    }
}
Simon Mourier
la source
10

Voici un exemple qui vous permet d'avoir une seule instance d'une application. Lorsqu'une nouvelle instance se charge, 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);
}
Nathan Moinvaziri
la source
Ceci est un très bel exemple de ce que je dois faire. Nathan, tous les arguments sont-ils envoyés en utilisant cette méthode? J'en ai 7 environ dans mon application et je pense que ce code fonctionnera.
kevp
1
Dans mon exemple, seul le premier argument est envoyé, mais il peut être modifié pour que tous soient envoyés.
Nathan Moinvaziri
8

Quelques réflexions: dans certains cas, une seule instance d'une application n'est pas "boiteuse", comme certains voudraient vous le faire croire. Les applications de base de données, etc. sont un ordre de grandeur plus difficile 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 met à jour tous les enregistrements ouverts dans plusieurs instances de l'application sur les utilisateurs machine, 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 chances de collision de nom sont tombées 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'intégrer dans son application, vous êtes à peu près une cible de toute façon et devrez faire BEAUCOUP plus pour vous protéger que de simplement jouer un nom de mutex. De plus, si l'on utilise la variante de: nouveau Mutex (vrai, "certains GUID plus Nom", out AIsFirstInstance), vous avez déjà votre indicateur pour savoir si le Mutex est la première instance ou non.

Bruce
la source
6

Autant de réponses à une question aussi simple en apparence. Juste pour secouer 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 vous voit que l'utiliser pour une petite partie de votre code et veut le marquer comme prêt pour la collecte des ordures. Il veut à peu près vous surpasser en pensant que vous n'utiliserez pas 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 ramasseur de déchets de vous laisser Mutex seul est de lui dire de le garder en vie à travers les différentes générations de collecte de garage. Exemple:

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

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

Peter
la source
3
Ne serait-il pas plus facile d'en stocker une copie partagée dans la classe d'application?
rossisdead
6

Il semble qu'il existe un très bon moyen de gérer cela:

Application à instance unique WPF

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

Joel Barsotti
la source
Cela ne semblait pas mettre la fenêtre existante au premier plan lorsque je l'ai essayée.
RandomEngy
6

Le code suivant est ma solution de canaux nommés WCF pour enregistrer une application à instance unique. C'est agréable car il 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 orienté vers WPF car il utilise le System.Windows.StartupEventHandler classe, mais cela pourrait être facilement modifié.

Ce code nécessite une référence à PresentationFramework, etSystem.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);
      }
    }
  }
}
Dan
la source
5

Vous ne devez jamais utiliser un mutex nommé pour implémenter une application à instance unique (ou du moins pas pour le code de production). Un code malveillant peut facilement DoS ( Denial of Service ) votre cul ...

Matt Davison
la source
8
"Vous ne devriez jamais utiliser un mutex nommé" - ne dites jamais jamais. Si un code malveillant s'exécute sur ma machine, je suis probablement déjà arrosé.
Joe
En fait, il n'a même pas besoin d'être un code malveillant. Il pourrait simplement s'agir d'une collision de noms accidentelle.
Matt Davison
Alors que devez-vous faire?
Kevin Berridge
La meilleure question est de savoir pourquoi vous souhaiteriez ce comportement. Ne concevez pas votre application comme une application à instance unique =). Je sais que c'est une réponse boiteuse mais d'un point de vue design, c'est presque toujours la bonne réponse. Sans en savoir plus sur l'application, il est difficile d'en dire beaucoup plus.
Matt Davison
2
Au moins sous Windows, les Mutex ont un contrôle d'accès, donc on peut jouer avec votre objet. Quant aux collisions de noms elles-mêmes, c'est pourquoi les UUID / GUID ont été inventés.
NuSkooler du
5

Regardez le code suivant. Il s'agit d'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();
    }
}
carlito
la source
4

Voici ce que j'utilise. Il a combiné 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();
        }
    }
Sergey Aldoukhov
la source
4

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 basé sur la classe Microsoft WindowsFormsApplicationBase standard.

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 référencer Microsoft.VisualBasic. Si vous souhaitez simplement réactiver l'application en cours d'exécution en tant que fenêtre normale lorsque l'utilisateur essaie de redémarrer le programme en cours d'exécution, le deuxième paramètre du SingleInstanceController peut être nul. Dans l'exemple donné, la fenêtre est agrandie.

Mikhail Semenov
la source
4

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

À 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 l'association de fichiers est requise, une application doit (selon l'attente standard des utilisateurs) être instanciée pour chaque document. Si vous devez passer des arguments à une application existante, je pense que j'utiliserais la DLL vb.

Ne passant pas d'arguments (juste une application à instance unique), je préfère ne pas enregistrer un nouveau message Windows et ne pas remplacer la boucle de message telle que définie dans Matt Davis Solution. Bien que ce ne soit pas un gros problème d'ajouter une DLL VisualBasic, mais je préfère ne pas ajouter une nouvelle référence juste pour faire une application à instance unique. De plus, je préfère instancier une nouvelle classe avec Main au lieu d'appeler Shutdown depuis App.Startup override pour m'assurer de quitter le plus tôt possible.

En espérant que tout le monde l'appréciera ... ou inspirera un peu :-)

La classe de démarrage du projet doit être définie comme «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);
                        }
                    }
                }
            }
        }
    }
}

WindowHelper:

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);
Eric Ouellet
la source
3

Ne pas utiliser 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à la ifdéclaration et mettre "Application déjà en cours d'exécution".
Cela pourrait être utile à quelqu'un.

newbieguy
la source
4
Si deux processus démarrent en même temps, ils peuvent tous les deux voir deux processus actifs et se terminer automatiquement.
AU
@AT Oui, cela peut également être utile pour les applications fonctionnant en tant qu'administrateur ou autre
newbieguy
Si vous faites une copie de votre application et la renommez, vous pouvez exécuter l'original et la copie en même temps.
Dominique Bijnens
2

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 exemple, plusieurs processus démarrés en même temps peuvent tous se terminer automatiquement en fonction du moment). Les approches basées sur le système de fenêtrage ne sont pas souhaitables dans une application console. Cette solution, basée sur la réponse de Divin, répond à 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.");
            }
        }
    }
}
À
la source
2

J'utilise Mutex dans ma solution pour empêcher plusieurs instances.

static Mutex mutex = null;
//A string that is the name of the mutex
string mutexName = @"Global\test";
//Prevent Multiple Instances of Application
bool onlyInstance = false;
mutex = new Mutex(true, mutexName, out onlyInstance);

if (!onlyInstance)
{
  MessageBox.Show("You are already running this application in your system.", "Already Running..", MessageBoxButton.OK);
  Application.Current.Shutdown();
}
Vishnu Babu
la source
1

Utilisez une 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;
            }
        }
    }
}
}
Cornel Marian
la source
1

Voici une solution légère que j'utilise qui permet à l'application de mettre au premier plan une fenêtre déjà existante sans avoir recours à des messages Windows personnalisés ni à rechercher aveuglément 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;
}

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

Jason Lim
la source
1

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

Antoine Diekmann
la source
1

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

Apparemment, la méthode post-message fonctionne correctement, si l'application n'est pas affichée dans la barre des tâches, cependant 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);
}
Martin Bech
la source
1

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();
    }
}
Siarhei Kuchuk
la source
1

[J'ai fourni un exemple de code pour les applications console et wpf ci-dessous.]

Il vous suffit de vérifier la valeur du createdNew variable (exemple ci-dessous!), Après avoir créé l'instance Mutex nommée.

Le booléen createdNewretournera false:

si l'instance Mutex nommée "YourApplicationNameHere" a déjà été créée quelque part sur le système

Le booléen createdNewretournera vrai:

s'il s'agit du premier Mutex nommé "YourApplicationNameHere" sur le système.


Application console - Exemple:

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

Exemple WPF:

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);
}
}
Légendes
la source
1

Une solution de gain de temps pour C # Winforms ...

Program.cs:

using System;
using System.Windows.Forms;
// needs reference to Microsoft.VisualBasic
using Microsoft.VisualBasic.ApplicationServices;  

namespace YourNamespace
{
    public class SingleInstanceController : WindowsFormsApplicationBase
    {
        public SingleInstanceController()
        {
            this.IsSingleInstance = true;
        }

        protected override void OnStartupNextInstance(StartupNextInstanceEventArgs e)
        {
            e.BringToForeground = true;
            base.OnStartupNextInstance(e);
        }

        protected override void OnCreateMainForm()
        {
            this.MainForm = new Form1();
        }
    }

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            string[] args = Environment.GetCommandLineArgs();
            SingleInstanceController controller = new SingleInstanceController();
            controller.Run(args);
        }
    }
}
AJBauer
la source
1

Veuillez vérifier la solution proposée à partir d' ici qui utilise un sémaphore pour déterminer si une instance existante est déjà en cours d'exécution, fonctionne pour une application WPF et peut passer des arguments de la deuxième instance à la première instance déjà en cours d'exécution en utilisant un TcpListener et un TcpClient:

Il fonctionne également pour .NET Core, pas seulement pour .NET Framework.

Alexandru Dicu
la source
1

Je ne trouve pas de solution courte ici, donc j'espère que quelqu'un aimera ceci:

MISE À JOUR 2018-09-20

Mettez ce code dans votre Program.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;
    }

    // Optional code. If you don't want that someone runs your ".exe" with a different name:

    string exeName = AppDomain.CurrentDomain.FriendlyName;
    // in debug mode, don't forget that you don't use your normal .exe name.
    // Debug uses the .vshost.exe.
    if (exeName != "the name of your executable.exe") 
    {
        // You can add a MessageBox here if you want.
        // To point out to 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 your executable.exe\"", 
            "Wrong executable name", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

    // Following code is default code:
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}
Deniz
la source
Cela introduira une condition de concurrence. Doit utiliser un mutex.
georgiosd
1
rien ne garantit que si vous faites tourner deux instances en même temps, cela fonctionnera. Comme mettre à jour une variable à partir de deux threads différents. Entreprise risquée et délicate. Utilisez la force, Luke :)
georgiosd
@georgiosd ah je vois ce que tu veux dire. Comme si quelqu'un démarre le .exe et change le nom. Oui, ce serait un moyen de le démarrer plusieurs fois, mais normalement le .exe ne fonctionne pas si le nom a été changé. Je mettrai à jour ma réponse ^^ Merci Luke: D de l'avoir signalé :)
Deniz
1
Pas seulement @Deniz. Si vous démarrez deux processus très rapidement, il est possible que la liste des processus ou la méthode qui les récupère s'exécute alors qu'il n'y en a encore qu'un qui apparaît. Cela peut être un cas de bord qui n'est pas pertinent pour vous, mais c'est une question générale ...
georgiosd
@georgiosd Pouvez-vous le prouver? Parce qu'Iv'e l'a testé juste pour toi hehe. Mais ce n'était pas possible pour moi, même vraiment "très vite"! : P Je ne comprends donc pas pourquoi vous croyez en quelque chose qui n'est tout simplement pas le cas et que vous n'aimez même pas ce code innocent: D
Deniz