Was ist der richtige Weg, um eine WPF-Anwendung mit einer Instanz zu erstellen?

655

Wie kann mit C # und WPF unter .NET (anstelle von Windows Forms oder Konsole) eine Anwendung erstellt werden, die nur als einzelne Instanz ausgeführt werden kann?

Ich weiß, dass es etwas mit einer mythischen Sache zu tun hat, die Mutex genannt wird. Selten kann ich jemanden finden, der sich die Mühe macht, anzuhalten und zu erklären, was eine davon ist.

Der Code muss auch die bereits ausgeführte Instanz darüber informieren, dass der Benutzer versucht hat, eine zweite zu starten, und möglicherweise auch Befehlszeilenargumente übergeben, falls vorhanden.

Nidonocu
quelle
14
Gibt die CLR nicht automatisch unveröffentlichte Mutexe frei, wenn die Anwendung trotzdem beendet wird?
Cocowalla
1
@Cocowalla: Der Finalizer sollte die nicht verwalteten Mutexe entsorgen, es sei denn, er kann nicht wissen, ob der Mutex von der verwalteten App erstellt oder an eine vorhandene angehängt wurde.
Ignacio Soler Garcia
Es ist sinnvoll, nur eine Instanz Ihrer App zu haben. Das Übergeben von Argumenten an eine bereits vorhandene App erscheint mir jedoch etwas albern. Ich sehe keinen Grund dafür. Wenn Sie eine App mit einer Dateierweiterung verknüpfen, sollten Sie so viele Apps öffnen, wie der Benutzer Dokumente öffnen möchte. Das ist das Standardverhalten, das jeder Benutzer erwarten würde.
Eric Ouellet
9
@Cocowalla Die CLR verwaltet keine nativen Ressourcen. Wenn ein Prozess beendet wird, werden jedoch alle Handles vom System freigegeben (das Betriebssystem, nicht die CLR).
Unsichtbarer
1
Ich bevorzuge die Antwort von @huseyint. Es verwendet die Microsoft-Klasse 'SingleInstance.cs', sodass Sie sich keine Gedanken über Mutexe und IntPtrs machen müssen. Auch keine Abhängigkeit von VisualBasic (yuk). Siehe codereview.stackexchange.com/questions/20871/… für mehr ...
Heliac

Antworten:

537

Hier ist ein sehr guter Artikel zur Mutex-Lösung. Der im Artikel beschriebene Ansatz ist aus zwei Gründen vorteilhaft.

Erstens ist keine Abhängigkeit von der Microsoft.VisualBasic-Assembly erforderlich. Wenn mein Projekt bereits von dieser Assembly abhängig wäre, würde ich wahrscheinlich empfehlen, den in einer anderen Antwort gezeigten Ansatz zu verwenden . Aber so wie es ist, verwende ich nicht die Microsoft.VisualBasic-Assembly und möchte meinem Projekt lieber keine unnötige Abhängigkeit hinzufügen.

Zweitens zeigt der Artikel, wie die vorhandene Instanz der Anwendung in den Vordergrund gerückt wird, wenn der Benutzer versucht, eine andere Instanz zu starten. Das ist eine sehr nette Geste, die die anderen hier beschriebenen Mutex-Lösungen nicht ansprechen.


AKTUALISIEREN

Zum 01.08.2014 ist der Artikel, auf den ich oben verlinkt habe, noch aktiv, aber der Blog wurde seit einiger Zeit nicht mehr aktualisiert. Das macht mir Sorgen, dass es irgendwann verschwinden könnte und damit die befürwortete Lösung. Ich reproduziere den Inhalt des Artikels hier für die Nachwelt. Die Wörter gehören ausschließlich dem Blog-Inhaber von Sanity Free Coding .

Heute wollte ich einen Code umgestalten, der es meiner Anwendung untersagte, mehrere Instanzen von sich selbst auszuführen.

Zuvor hatte ich System.Diagnostics.Process verwendet , um in der Prozessliste nach einer Instanz meiner myapp.exe zu suchen. Während dies funktioniert, bringt es viel Aufwand mit sich, und ich wollte etwas saubereres.

Da ich wusste, dass ich dafür einen Mutex verwenden könnte (aber noch nie zuvor), machte ich mich daran, meinen Code zu reduzieren und mein Leben zu vereinfachen.

In der Klasse meines Application Main habe ich eine Statik namens Mutex erstellt :

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

Mit einem benannten Mutex können wir die Synchronisation über mehrere Threads und Prozesse hinweg stapeln, was genau die Magie ist, nach der ich suche.

Mutex.WaitOne hat eine Überlastung, die angibt, wie lange wir warten müssen. Da wir unseren Code eigentlich nicht synchronisieren möchten (überprüfen Sie einfach, ob er derzeit verwendet wird), verwenden wir die Überladung mit zwei Parametern: Mutex.WaitOne (Timepan-Timeout, bool exitContext) . Warten Sie, bis true zurückgegeben wird, wenn es eingegeben werden kann, und false, wenn dies nicht der Fall ist. In diesem Fall möchten wir überhaupt nicht warten. Wenn unser Mutex verwendet wird, überspringen Sie ihn und fahren Sie fort. Geben Sie also TimeSpan.Zero ein (warten Sie 0 Millisekunden) und setzen Sie den exitContext auf true, damit wir den Synchronisationskontext beenden können, bevor wir versuchen, eine Sperre für ihn zu erlangen. Auf diese Weise verpacken wir unseren Application.Run-Code wie folgt:

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

Wenn unsere App ausgeführt wird, gibt WaitOne false zurück und wir erhalten ein Meldungsfeld.

Anstatt ein Meldungsfeld anzuzeigen, habe ich mich dafür entschieden, ein wenig Win32 zu verwenden, um meine laufende Instanz zu benachrichtigen, dass jemand vergessen hat, dass sie bereits ausgeführt wird (indem ich mich an die Spitze aller anderen Fenster gebracht habe). Um dies zu erreichen, habe ich PostMessage verwendet , um eine benutzerdefinierte Nachricht an jedes Fenster zu senden (die benutzerdefinierte Nachricht wurde von meiner laufenden Anwendung bei RegisterWindowMessage registriert , was bedeutet, dass nur meine Anwendung weiß, was es ist). Dann wird meine zweite Instanz beendet. Die laufende Anwendungsinstanz würde diese Benachrichtigung erhalten und verarbeiten. Zu diesem Zweck habe ich WndProc in meinem Hauptformular überschrieben und auf meine benutzerdefinierte Benachrichtigung gewartet . Als ich diese Benachrichtigung erhielt, setzte ich die TopMost-Eigenschaft des Formulars auf true, um es oben aufzurufen.

Folgendes habe ich erreicht:

  • 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 (Vorderseite teilweise)
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
quelle
5
Auf der Grundlage, dass diese Antwort weniger Code und weniger Bibliotheken verwendet und die höchste Funktionalität bietet, werde ich dies zur neu akzeptierten Antwort machen. Wenn jemand einen korrekteren Weg kennt, um das Formular mithilfe von APIs nach oben zu bringen, können Sie dies gerne hinzufügen.
Nidonocu
11
@BlueRaja, du startest die erste App-Instanz. Wenn Sie die zweite App-Instanz starten, erkennt sie, dass bereits eine andere Instanz ausgeführt wird, und bereitet das Herunterfahren vor. Zuvor sendet es eine native "SHOWME" -Nachricht an die erste Instanz, die die erste Instanz nach oben bringt. Ereignisse in .NET erlauben keine prozessübergreifende Kommunikation, weshalb die native Nachricht verwendet wird.
Matt Davis
7
Gibt es vielleicht eine Möglichkeit, die Befehlszeilen der anderen Instanz zu übergeben?
Gyurisc
22
@Nam, der MutexKonstruktor benötigt lediglich eine Zeichenfolge, sodass Sie einen beliebigen Zeichenfolgennamen angeben können , z. B. "This Is My Mutex". Da ein 'Mutex' ein Systemobjekt ist, das anderen Prozessen zur Verfügung steht, möchten Sie normalerweise, dass der Name eindeutig ist, damit er nicht mit anderen 'Mutex'-Namen auf demselben System kollidiert. In dem Artikel ist die kryptisch aussehende Zeichenfolge eine 'Guid'. Sie können dies programmgesteuert durch Aufrufen generieren System.Guid.NewGuid(). Im Fall des Artikels hat der Benutzer ihn wahrscheinlich über Visual Studio generiert, wie hier gezeigt: msdn.microsoft.com/en-us/library/ms241442(VS.80).aspx
Matt Davis
6
Geht der Mutex-Ansatz davon aus, dass derselbe Benutzer versucht, die Anwendung erneut zu starten? Sicherlich macht es keinen Sinn, "die vorhandene Instanz der Anwendung in den Vordergrund zu stellen", nachdem ein Benutzer
gewechselt wurde
107

Sie könnten die Mutex-Klasse verwenden, aber Sie werden bald feststellen, dass Sie den Code implementieren müssen, um die Argumente und dergleichen selbst zu übergeben. Nun, ich habe einen Trick beim Programmieren in WinForms gelernt, als ich Chris Sells Buch gelesen habe . Dieser Trick verwendet eine Logik, die uns bereits im Framework zur Verfügung steht. Ich weiß nichts über dich, aber wenn ich etwas über Dinge lerne, die ich im Framework wiederverwenden kann, ist das normalerweise der Weg, den ich gehe, anstatt das Rad neu zu erfinden. Es sei denn natürlich, es macht nicht alles, was ich will.

Als ich zu WPF kam, hatte ich eine Möglichkeit, denselben Code zu verwenden, jedoch in einer WPF-Anwendung. Diese Lösung sollte Ihren Anforderungen anhand Ihrer Frage entsprechen.

Zuerst müssen wir unsere Anwendungsklasse erstellen. In dieser Klasse überschreiben wir das OnStartup-Ereignis und erstellen eine Methode namens Activate, die später verwendet wird.

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

Zweitens müssen wir eine Klasse erstellen, die unsere Instanzen verwalten kann. Bevor wir das durchgehen, werden wir tatsächlich Code wiederverwenden, der sich in der Microsoft.VisualBasic-Assembly befindet. Da ich in diesem Beispiel C # verwende, musste ich auf die Assembly verweisen. Wenn Sie VB.NET verwenden, müssen Sie nichts tun. Die Klasse, die wir verwenden werden, ist WindowsFormsApplicationBase und erbt unseren Instanzmanager davon und nutzt dann Eigenschaften und Ereignisse, um die einzelne Instanzierung zu verarbeiten.

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

Grundsätzlich verwenden wir die VB-Bits, um einzelne Instanzen zu erkennen und entsprechend zu verarbeiten. OnStartup wird ausgelöst, wenn die erste Instanz geladen wird. OnStartupNextInstance wird ausgelöst, wenn die Anwendung erneut ausgeführt wird. Wie Sie sehen können, kann ich über die Ereignisargumente zu dem gelangen, was in der Befehlszeile übergeben wurde. Ich habe den Wert auf ein Instanzfeld gesetzt. Sie können die Befehlszeile hier analysieren oder sie über den Konstruktor und den Aufruf der Activate-Methode an Ihre Anwendung übergeben.

Drittens ist es Zeit, unseren EntryPoint zu erstellen. Anstatt die Anwendung wie gewohnt neu zu gestalten, werden wir unseren SingleInstanceManager nutzen.

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

Nun, ich hoffe, Sie können alles verfolgen und diese Implementierung verwenden und zu Ihrer eigenen machen.

Dale Ragan
quelle
9
Ich würde bei der Mutex-Lösung bleiben, weil sie nichts mit Formularen zu tun hat.
Steven Sudit
1
Ich habe dies verwendet, weil ich Probleme mit anderen Ansätzen hatte, aber ich bin mir ziemlich sicher, dass es Remoting unter der Haube verwendet. Meine App hatte zwei verwandte Probleme - einige Kunden sagen, dass sie versucht, nach Hause zu telefonieren, obwohl sie es nicht gesagt haben. Wenn sie genauer hinschauen, ist die Verbindung zu localhost. Trotzdem wissen sie das zunächst nicht. Außerdem kann ich Remoting nicht für einen anderen Zweck verwenden (glaube ich?), Da es bereits dafür verwendet wird. Wenn ich den Mutex-Ansatz ausprobierte, konnte ich das Remoting erneut verwenden.
Richard Watson
4
Verzeihen Sie mir, aber wenn mir etwas fehlt, haben Sie es vermieden, 3 Codezeilen zu schreiben, und stattdessen haben Sie das Framework wiederverwendet, nur um ziemlich schweren Code dafür zu schreiben. Wo sind also die Einsparungen?
Greenoldman
2
ist es möglich, es in Winforms zu tun?
Jack
1
Wenn Sie InitializeComponent () in der Anwendungsinstanz nicht aufrufen, können Sie keine Ressourcen auflösen ... _application = new SingleInstanceApplication (); _application.InitializeComponent (); _application.Run ();
Nick
84

Von hier aus .

Eine übliche Verwendung für einen prozessübergreifenden Mutex besteht darin, sicherzustellen, dass jeweils nur eine Instanz eines Programms ausgeführt werden kann. So geht's:

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

Eine gute Funktion von Mutex ist, dass die CLR den Mutex automatisch freigibt, wenn die Anwendung beendet wird, ohne dass ReleaseMutex zuerst aufgerufen wird.

Jason Saldo
quelle
5
Ich muss sagen, ich mag diese Antwort viel mehr als die akzeptierte, nur weil sie nicht von WinForms abhängig ist. Persönlich hat sich der größte Teil meiner Entwicklung auf WPF verlagert, und ich möchte für so etwas keine WinForm-Bibliotheken abrufen müssen.
Switters
5
Um eine vollständige Antwort zu erhalten, müssen Sie natürlich auch beschreiben, wie Sie die Argumente an die andere Instanz übergeben :)
Simon Buchan
@ Jason, gut, danke! Aber ich ziehe es vor, keine Auszeit zu verpassen. Es ist so subjektiv und hängt von so vielen Variablen ab. Wenn Sie jemals den Start einer anderen App aktivieren möchten, geben Sie Ihren Mutex einfach schneller frei. Zum Beispiel, sobald der Benutzer das Schließen bestätigt
Eric Ouellet
@EricOuellet: Fast jedes Programm mit Registerkarten tut dies - Photoshop, Sublime Text, Chrome ... Wenn Sie einen guten Grund haben, einen "Master" -Prozess durchzuführen (sagen wir, Sie haben eine In-Proc-Datenbank für Einstellungen), können Sie dies tun Ich möchte, dass die Benutzeroberfläche so angezeigt wird, als wäre es auch ein neuer Prozess.
Simon Buchan
@ Simon, du hast recht. Ich frage mich nur über eine sehr alte Sache ... MDI vs SDI (Multi Documentinterface vs Single Document Interface). Wenn Sie über Registerkarten sprechen, beziehen Sie sich auf MDI. In einem Microsoft-Buch wird 1998 vorgeschlagen, jede MDI-App zu entfernen. Microsoft hat Word, Excel ... auf SDI umgestellt, was meiner Meinung nach einfacher und besser ist. Ich verstehe, dass Chrome und andere (jetzt IE) zurück zu MDI wollen. Ich persönlich (basierend auf nichts / persönlichen Gefühlen), dass es immer noch besser ist, eine neue App zu öffnen, wenn File Assoc ausgewählt ist. Aber ich verstehe die jetzt gestellte Frage besser. Vielen Dank !
Eric Ouellet
58

MSDN hat tatsächlich eine Beispielanwendung für C # und VB, um genau dies zu tun: http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx

Die gebräuchlichste und zuverlässigste Methode zum Entwickeln der Einzelinstanzerkennung ist die Verwendung der Microsoft .NET Framework-Remoting-Infrastruktur (System.Remoting). Microsoft .NET Framework (Version 2.0) enthält den Typ WindowsFormsApplicationBase, der die erforderlichen Remoting-Funktionen kapselt. Um diesen Typ in eine WPF-Anwendung zu integrieren, muss ein Typ davon abgeleitet und als Shim zwischen der statischen Einstiegspunktmethode der Anwendung Main und dem Anwendungstyp der WPF-Anwendung verwendet werden. Der Shim erkennt, wann eine Anwendung zum ersten Mal gestartet wird und wann nachfolgende Starts versucht werden, und steuert den WPF-Anwendungstyp, um zu bestimmen, wie die Starts verarbeitet werden sollen.

  • Für C # -Leute atmen Sie einfach tief ein und vergessen das Ganze "Ich möchte VisualBasic DLL nicht einschließen". Aus diesem Grund und aufgrund der Aussagen von Scott Hanselman und der Tatsache, dass dies so ziemlich die sauberste Lösung für das Problem ist und von Leuten entwickelt wurde, die viel mehr über das Framework wissen als Sie.
  • Unter dem Gesichtspunkt der Benutzerfreundlichkeit ist die Tatsache, dass Ihr Benutzer eine Anwendung lädt und diese bereits geöffnet ist und Sie ihm eine Fehlermeldung geben, als ob 'Another instance of the app is running. Bye'er kein sehr zufriedener Benutzer wäre. Sie MÜSSEN einfach (in einer GUI-Anwendung) zu dieser Anwendung wechseln und die angegebenen Argumente übergeben - oder wenn Befehlszeilenparameter keine Bedeutung haben, müssen Sie die möglicherweise minimierte Anwendung öffnen.

Das Framework hat bereits Unterstützung dafür - es ist nur so, dass ein Idiot die DLL genannt hat Microsoft.VisualBasicund es nicht in so Microsoft.ApplicationUtilsetwas hineingesteckt wurde . Überwinde es - oder öffne Reflector.

Tipp: Wenn Sie diesen Ansatz genau so verwenden, wie er ist, und Sie bereits eine App.xaml mit Ressourcen usw. haben, sollten Sie sich dies auch ansehen .

Simon_Weaver
quelle
Vielen Dank, dass Sie den Link "Schauen Sie sich das auch an" hinzugefügt haben. Genau das brauchte ich. Übrigens ist Lösung 3 in Ihrem Link die beste.
Eternal21
Ich bin auch ein Befürworter der Delegation an das Framework und speziell gestaltete Bibliotheken, wo dies möglich ist.
Eniola
23

Dieser Code sollte zur Hauptmethode gehen. Schauen Sie sich hier für weitere Informationen über die wichtigste Methode in 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; 
    }
}

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

Hinweis: Bei den oben genannten Methoden wird davon ausgegangen, dass Ihr Prozess / Ihre Anwendung einen eindeutigen Namen hat. Weil es den Prozessnamen verwendet, um festzustellen, ob vorhandene Prozessoren vorhanden sind. Wenn Ihre Anwendung einen sehr gebräuchlichen Namen hat (z. B. Editor), funktioniert der oben beschriebene Ansatz nicht.

CharithJ
quelle
1
Dies funktioniert auch nicht, wenn auf Ihrem Computer ein anderes Programm mit demselben Namen ausgeführt wird. ProcessNameGibt den Namen der ausführbaren Datei abzüglich der zurück exe. Wenn Sie eine Anwendung mit dem Namen "Notepad" erstellen und der Windows-Editor ausgeführt wird, wird er als Ihre Anwendung erkannt.
Jcl
1
Danke für diese Antwort. Ich habe so viele ähnliche Fragen gefunden und die Antworten waren immer so ausführlich und / oder verwirrend, dass ich sie für nutzlos befunden habe. Diese (Methode 1) ist unkompliziert, klar und hat mir vor allem dabei geholfen, meinen Code zum Laufen zu bringen.
ElDoRado1239
20

Nun, ich habe eine Einwegklasse dafür, die für die meisten Anwendungsfälle problemlos funktioniert:

Verwenden Sie es so:

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

        // Initialize program here.
    }
}

Hier ist es:

/// <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
quelle
1
Dieser war ziemlich einfach zu arbeiten. Die zweite Anwendung wird erst geschlossen, wenn ich Application.Exit () geändert habe. zu einer einfachen Rückkehr; aber sonst ist es großartig. Obwohl ich zugebe, dass ich mir die vorherige Lösung genauer ansehen werde, da sie eine Schnittstelle verwendet. blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/…
hal9000
15

Eine neue Anwendung, die Mutex- und IPC-Inhalte verwendet und außerdem alle Befehlszeilenargumente an die ausgeführte Instanz übergibt, ist WPF Single Instance Application .

huseyint
quelle
Ich benutze dies mit großem Erfolg. Wenn Sie NamedPipes einbinden, können Sie auch Befehlszeilenargumente an die ursprüngliche Anwendung übergeben. Die Klasse 'SingleInstance.cs' wurde von Microsoft geschrieben. Ich habe einen weiteren Link zu einer besser lesbaren Version von Arik Poznanskis Blog auf CodeProject hinzugefügt.
Heliac
11

Die Code C # .NET-Einzelinstanzanwendung , die als Referenz für die markierte Antwort dient, ist ein guter Anfang.

Ich habe jedoch festgestellt, dass es nicht sehr gut mit den Fällen umgeht, in denen für die bereits vorhandene Instanz ein modales Dialogfeld geöffnet ist, unabhängig davon, ob es sich um ein verwaltetes (wie ein anderes Formular wie ein About-Feld) oder ein nicht verwaltetes (wie das) handelt OpenFileDialog auch bei Verwendung der Standard-.NET-Klasse). Mit dem ursprünglichen Code wird das Hauptformular aktiviert, das modale bleibt jedoch inaktiv, was seltsam aussieht. Außerdem muss der Benutzer darauf klicken, um die App weiterhin verwenden zu können.

Daher habe ich eine SingleInstance-Dienstprogrammklasse erstellt, um all dies für Winforms- und WPF-Anwendungen ganz automatisch zu erledigen.

Winforms :

1) Ändern Sie die Programmklasse wie folgt:

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) Ändern Sie die Hauptfensterklasse wie folgt:

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) Ändern Sie die App-Seite wie folgt (und stellen Sie sicher, dass Sie die Erstellungsaktion auf Seite setzen, um die Hauptmethode neu definieren zu können):

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) Ändern Sie die Hauptfensterklasse wie folgt:

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

Und hier ist die Utility-Klasse:

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
quelle
10

Hier ist ein Beispiel, mit dem Sie eine einzelne Instanz einer Anwendung haben können. Wenn neue Instanzen geladen werden, übergeben sie ihre Argumente an die Hauptinstanz, die ausgeführt wird.

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
quelle
Dies ist ein wirklich schönes Beispiel dafür, was ich tun soll. Nathan, werden alle Argumente mit dieser Methode gesendet? Ich habe 7 oder so in meiner App und ich denke, dass dieser Code funktionieren wird.
Kevp
1
In meinem Beispiel wird nur das erste Argument gesendet, es kann jedoch so geändert werden, dass alle gesendet werden.
Nathan Moinvaziri
8

Nur ein paar Gedanken: Es gibt Fälle, in denen nur eine Instanz einer Anwendung nicht "lahm" sein muss, wie manche glauben machen würden. Datenbank-Apps usw. sind um eine Größenordnung schwieriger, wenn einem einzelnen Benutzer mehrere Instanzen der App für den Zugriff auf eine Datenbank zugelassen werden (Sie wissen, dass alle Datensätze aktualisiert werden, die in mehreren Instanzen der App für die Benutzer geöffnet sind Maschine usw.). Verwenden Sie für die Sache "Namenskollision" zunächst keinen von Menschen lesbaren Namen - verwenden Sie stattdessen eine GUID oder, noch besser, eine GUID + den von Menschen lesbaren Namen. Die Wahrscheinlichkeit einer Namenskollision ist einfach vom Radar gefallen, und der Mutex kümmert sich nicht darum Wie jemand betonte, würde ein DOS-Angriff scheiße sein, aber wenn die böswillige Person sich die Mühe gemacht hat, den Mutex-Namen zu erhalten und ihn in ihre App zu integrieren, Sie sind sowieso so ziemlich ein Ziel und müssen VIEL mehr tun, um sich zu schützen, als nur einen Mutex-Namen zu fummeln. Wenn man die Variante von: new Mutex (true, "einige GUID plus Name", aus AIsFirstInstance) verwendet, haben Sie bereits Ihren Indikator, ob der Mutex die erste Instanz ist oder nicht.

Bruce
quelle
6

So viele Antworten auf eine so scheinbar einfache Frage. Nur um die Dinge hier ein wenig aufzurütteln, ist meine Lösung für dieses Problem.

Das Erstellen eines Mutex kann problematisch sein, da der JIT-er sieht, dass Sie ihn nur für einen kleinen Teil Ihres Codes verwenden, und ihn als bereit für die Speicherbereinigung markieren möchte. Es möchte Sie so ziemlich überraschen, dass Sie denken, dass Sie diesen Mutex nicht so lange verwenden werden. In Wirklichkeit möchten Sie an diesem Mutex festhalten, solange Ihre Anwendung ausgeführt wird. Der beste Weg, dem Müllsammler zu sagen, er solle Mutex in Ruhe lassen, besteht darin, ihm zu sagen, er solle ihn über die verschiedenen Generationen der Garagensammlung hinweg am Leben erhalten. Beispiel:

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

Ich habe die Idee von dieser Seite gestrichen: http://www.ai.uga.edu/~mc/SingleInstance.html

Peter
quelle
3
Wäre es nicht einfacher, eine freigegebene Kopie davon in der Anwendungsklasse zu speichern?
Rossisdead
6

Es sieht so aus, als gäbe es einen wirklich guten Weg, damit umzugehen:

WPF-Einzelinstanzanwendung

Dies bietet eine Klasse, die Sie hinzufügen können und die alle Mutex- und Messaging-Probleme verwaltet, um die Implementierung so zu vereinfachen, dass sie einfach trivial ist.

Joel Barsotti
quelle
Dies schien das vorhandene Fenster nicht in den Vordergrund zu rücken, als ich es versuchte.
RandomEngy
6

Der folgende Code ist meine WCF-Lösung für Named Pipes zum Registrieren einer Einzelinstanzanwendung. Es ist schön, weil es auch ein Ereignis auslöst, wenn eine andere Instanz versucht zu starten, und die Befehlszeile der anderen Instanz empfängt.

Es ist auf WPF ausgerichtet, weil es die System.Windows.StartupEventHandlerKlasse verwendet, aber dies könnte leicht geändert werden.

Dieser Code erfordert einen Verweis auf PresentationFrameworkund System.ServiceModel.

Verwendungszweck:

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

Quellcode:

/// <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
quelle
5

Sie sollten niemals einen benannten Mutex verwenden, um eine Einzelinstanzanwendung zu implementieren (oder zumindest nicht für Produktionscode). Bösartiger Code kann leicht DoS ( Denial of Service ) Ihren Arsch ...

Matt Davison
quelle
8
"Sie sollten niemals einen benannten Mutex verwenden" - sagen Sie niemals nie. Wenn auf meinem Computer schädlicher Code ausgeführt wird, bin ich wahrscheinlich bereits abgespritzt.
Joe
Eigentlich muss es nicht einmal bösartiger Code sein. Es könnte nur eine zufällige Namenskollision sein.
Matt Davison
Was solltest du dann tun?
Kevin Berridge
Die bessere Frage ist, aus welchem ​​möglichen Grund Sie dieses Verhalten wünschen würden. Entwerfen Sie Ihre App nicht als Einzelinstanzanwendung =). Ich weiß, dass dies eine lahme Antwort ist, aber vom Designstandpunkt aus ist es fast immer die richtige Antwort. Ohne mehr über die App zu wissen, ist es schwer, viel mehr zu sagen.
Matt Davison
2
Zumindest unter Windows haben Mutexe eine Zugriffskontrolle, sodass man mit Ihrem Objekt spielen kann. Um Kollisionen selbst zu benennen, wurden UUID / GUIDs erfunden.
NuSkooler
5

Schauen Sie sich den folgenden Code an. Es ist eine großartige und einfache Lösung, um mehrere Instanzen einer WPF-Anwendung zu verhindern.

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
quelle
4

Hier ist was ich benutze. Es kombinierte die Prozessaufzählung, um Switching und Mutex durchzuführen, um sich vor "aktiven Klickern" zu schützen:

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
quelle
4

Ich fand die einfachere Lösung, ähnlich der von Dale Ragan, aber leicht modifiziert. Es macht praktisch alles, was Sie brauchen, und basiert auf der Standardklasse Microsoft WindowsFormsApplicationBase.

Zunächst erstellen Sie die SingleInstanceController-Klasse, die Sie in allen anderen Einzelinstanzanwendungen verwenden können, die Windows Forms verwenden:

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

Dann können Sie es in Ihrem Programm wie folgt verwenden:

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

Sowohl das Programm als auch die SingleInstanceController_NET-Lösung sollten auf Microsoft.VisualBasic verweisen. Wenn Sie die laufende Anwendung nur als normales Fenster reaktivieren möchten, wenn der Benutzer versucht, das laufende Programm neu zu starten, kann der zweite Parameter im SingleInstanceController null sein. Im angegebenen Beispiel wird das Fenster maximiert.

Mikhail Semenov
quelle
4

Update 2017-01-25. Nachdem ich einige Dinge ausprobiert hatte, entschied ich mich für VisualBasic.dll, es ist einfacher und funktioniert besser (zumindest für mich). Ich lasse meine vorherige Antwort nur als Referenz ...

Nur als Referenz habe ich dies getan, ohne Argumente zu übergeben (was ich nicht begründen kann ... Ich meine eine einzelne App mit Argumenten, die von einer Instanz an eine andere weitergegeben werden sollen). Wenn eine Dateizuordnung erforderlich ist, sollte für jedes Dokument eine App (gemäß den Standarderwartungen des Benutzers) eingerichtet werden. Wenn Sie Argumente an eine vorhandene App übergeben müssen, würde ich wahrscheinlich vb dll verwenden.

Da keine Argumente übergeben werden (nur Einzelinstanz-App), möchte ich keine neue Window-Nachricht registrieren und die in Matt Davis Solution definierte Nachrichtenschleife nicht überschreiben. Es ist zwar keine große Sache, eine VisualBasic-DLL hinzuzufügen, aber ich ziehe es vor, keine neue Referenz hinzuzufügen, nur um eine Einzelinstanz-App zu erstellen. Außerdem bevorzuge ich es, eine neue Klasse mit Main zu instanziieren, anstatt Shutdown über App.Startup-Override aufzurufen, um sicherzustellen, dass das Programm so schnell wie möglich beendet wird.

In der Hoffnung, dass es irgendjemandem gefällt ... oder ein bisschen inspiriert :-)

Die Projektstartklasse sollte als 'SingleInstanceApp' festgelegt werden.

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
quelle
3

Ohne Mutex, einfache Antwort:

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

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

Legen Sie es in die Program.Main().
Beispiel :

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

Sie können MessageBox.Showder ifAnweisung hinzufügen und "Anwendung läuft bereits" eingeben.
Dies könnte für jemanden hilfreich sein.

Newbieguy
quelle
4
Wenn zwei Prozesse gleichzeitig gestartet werden, sehen beide möglicherweise zwei aktive Prozesse und beenden sich selbst.
AT
@AT Ja richtig, dies kann auch für Apps hilfreich sein, die als Administrator oder sonst ausgeführt werden
newbieguy
Wenn Sie eine Kopie Ihrer Anwendung erstellen und umbenennen, können Sie das Original und die Kopie gleichzeitig ausführen.
Dominique Bijnens
2

Named-Mutex-basierte Ansätze sind nicht plattformübergreifend, da Named-Mutexe in Mono nicht global sind. Auf Prozessaufzählungen basierende Ansätze haben keine Synchronisation und können zu falschem Verhalten führen (z. B. können mehrere gleichzeitig gestartete Prozesse je nach Zeitpunkt alle selbst beendet werden). Fenstersystembasierte Ansätze sind in einer Konsolenanwendung nicht wünschenswert. Diese Lösung, die auf der Antwort von Divin aufbaut, behebt all diese Probleme:

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.");
            }
        }
    }
}
BEIM
quelle
2

Ich verwende Mutex in meiner Lösung, um mehrere Instanzen zu verhindern.

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
quelle
1

Verwenden Sie eine Mutex-Lösung:

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
quelle
1

Hier ist eine einfache Lösung, mit der die Anwendung ein bereits vorhandenes Fenster in den Vordergrund stellen kann, ohne auf benutzerdefinierte Fenstermeldungen zurückgreifen oder Prozessnamen blind suchen zu müssen.

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

Bearbeiten: Sie können Mutex und createNew auch statisch speichern und initialisieren, aber Sie müssen den Mutex explizit entsorgen / freigeben, sobald Sie damit fertig sind. Persönlich bevorzuge ich es, den Mutex lokal zu halten, da er automatisch entsorgt wird, selbst wenn die Anwendung geschlossen wird, ohne jemals das Ende von Main zu erreichen.

Jason Lim
quelle
1

Ich habe der NativeMethods-Klasse eine sendMessage-Methode hinzugefügt.

Anscheinend funktioniert die Postmessage-Methode nicht, wenn die Anwendung nicht in der Taskleiste angezeigt wird. Die Verwendung der Sendmessage-Methode löst dies jedoch.

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
quelle
1

Hier ist das gleiche, was über Event implementiert wurde.

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
quelle
1

[Ich habe unten Beispielcode für Konsolen- und wpf-Anwendungen bereitgestellt.]

Sie müssen nur den Wert von überprüfen createdNew Variablen (Beispiel unten!), Nachdem Sie die benannte Mutex-Instanz erstellt haben.

Der Boolesche Wert createdNewgibt false zurück:

wenn die Mutex-Instanz mit dem Namen "YourApplicationNameHere" bereits irgendwo auf dem System erstellt wurde

Der Boolesche Wert createdNewgibt true zurück:

Wenn dies der erste Mutex mit dem Namen "YourApplicationNameHere" auf dem System ist.


Konsolenanwendung - Beispiel:

static Mutex m = null;

static void Main(string[] args)
{
    const string mutexName = "YourApplicationNameHere";
    bool createdNew = false;

    try
    {
        // Initializes a new instance of the Mutex class with a Boolean value that indicates 
        // whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex, 
        // and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.

        using (m = new Mutex(true, mutexName, out createdNew))
        {
            if (!createdNew)
            {
                Console.WriteLine("instance is alreday running... shutting down !!!");
                Console.Read();
                return; // Exit the application
            }

            // Run your windows forms app here
            Console.WriteLine("Single instance app is running!");
            Console.ReadLine();
        }


    }
    catch (Exception ex)
    {

        Console.WriteLine(ex.Message);
        Console.ReadLine();
    }
}

WPF-Beispiel:

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);
}
}
Legenden
quelle
1

Eine zeitsparende Lösung für 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
quelle
1

Bitte überprüfen Sie die vorgeschlagene Lösung von hier aus , dass Anwendungen einer Semaphore , um zu bestimmen , ob eine vorhandene Instanz bereits ausgeführt wird , arbeitet für eine Anwendung WPF und Argumente aus zweiter Instanz zur ersten bereits laufenden Instanz durch einen TcpListener verwenden und einen TcpClient passieren kann:

Es funktioniert auch für .NET Core, nicht nur für .NET Framework.

Alexandru Dicu
quelle
1

Ich kann hier keine kurze Lösung finden, also hoffe ich, dass dies jemandem gefällt:

AKTUALISIERT am 20.09.2018

Fügen Sie diesen Code in Ihr 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
quelle
Dies führt zu einer Rennbedingung. Muss einen Mutex verwenden.
Georgiosd
1
Es gibt keine Garantie dafür, dass dies funktioniert, wenn Sie zwei Instanzen gleichzeitig starten. Wie das Aktualisieren einer Variablen aus zwei verschiedenen Threads. Tricky riskantes Geschäft. Verwenden Sie die Kraft, Luke :)
Georgiosd
@georgiosd ah ich verstehe was du meinst. Zum Beispiel, wenn jemand die EXE-Datei startet und den Namen ändert. Ja, dies wäre eine Möglichkeit, es öfter zu starten, aber normalerweise funktioniert die EXE-Datei nicht, wenn der Name geändert wurde. Ich werde meine Antwort aktualisieren ^^ Danke Luke: D für den Hinweis :)
Deniz
1
Nicht nur das @Deniz. Wenn Sie zwei Prozesse sehr schnell starten, besteht die Möglichkeit, dass die Prozessliste oder die Methode, mit der sie abgerufen werden, ausgeführt wird, solange nur noch einer angezeigt wird. Dies mag ein
Randfall sein
@georgiosd Kannst du das beweisen? Weil ich es nur für dich getestet habe, hehe. Aber es war mir nicht möglich, auch nicht wirklich "sehr schnell"! : P Also ich kann nicht verstehen, warum Sie an etwas glauben, was einfach nicht der Fall ist und diesen unschuldigen Code sogar nicht mögen: D
Deniz