Qual è il modo corretto di creare un'applicazione WPF a istanza singola?


657

Utilizzando C # e WPF in .NET (anziché Windows Form o console), qual è il modo corretto di creare un'applicazione che può essere eseguita solo come singola istanza?

So che ha qualcosa a che fare con qualcosa di mitico chiamato mutex, raramente posso trovare qualcuno che si preoccupa di fermarsi e spiegare cosa sono questi.

Il codice deve anche informare l'istanza già in esecuzione che l'utente ha tentato di avviarne una seconda, e forse anche passare qualsiasi argomento della riga di comando, se presente.


14
Il CLR non rilascia automaticamente alcun mutex inedito quando l'applicazione termina comunque?
Cocowalla,

1
@Cocowalla: il finalizzatore dovrebbe eliminare i mutex non gestiti a meno che non sia in grado di sapere se il mutex è stato creato dall'app gestita o collegato a uno esistente.
Ignacio Soler Garcia,

Avere solo un'istanza della tua app è ragionevole. Ma passare argomenti a un'app già esistente mi sembra un po 'sciocco. Non riesco a vedere alcun motivo per farlo. Se associ un'app all'estensione del file, dovresti aprire tutte le app che l'utente desidera aprire i documenti. Questo è il comportamento standard che ogni utente si aspetterebbe.
Eric Ouellet,

9
@Cocowalla Il CLR non gestisce le risorse native. Tuttavia, se un processo termina, tutti gli handle vengono liberati dal sistema (il sistema operativo, non il CLR).
Probabile

1
Preferisco la risposta di @huseyint. Utilizza la classe "SingleInstance.cs" di Microsoft, quindi non devi preoccuparti di Mutex e IntPtrs. Inoltre, nessuna dipendenza da VisualBasic (yuk). Vedi codereview.stackexchange.com/questions/20871/… per ulteriori informazioni ...
Heliac,

Risposte:


537

Ecco un ottimo articolo sulla soluzione Mutex. L'approccio descritto nell'articolo è vantaggioso per due motivi.

Innanzitutto, non richiede una dipendenza dall'assembly Microsoft.VisualBasic. Se il mio progetto avesse già una dipendenza da quell'assemblea, probabilmente difenderei usando l'approccio mostrato in un'altra risposta . Tuttavia, non utilizzo l'assembly Microsoft.VisualBasic e preferirei non aggiungere una dipendenza non necessaria al mio progetto.

In secondo luogo, l'articolo mostra come portare in primo piano l'istanza esistente dell'applicazione quando l'utente tenta di avviare un'altra istanza. È un bel tocco che le altre soluzioni Mutex qui descritte non affrontano.


AGGIORNARE

A partire dall'8 / 1/2014, l'articolo a cui ho collegato sopra è ancora attivo, ma il blog non è stato aggiornato da un po '. Questo mi fa preoccupare che alla fine potrebbe scomparire, e con essa, la soluzione raccomandata. Sto riproducendo il contenuto dell'articolo qui per i posteri. Le parole appartengono esclusivamente al proprietario del blog di Sanity Free Coding .

Oggi volevo refactoring del codice che proibiva alla mia applicazione di eseguire più istanze di se stessa.

In precedenza avevo usato System.Diagnostics.Process per cercare un'istanza di myapp.exe nell'elenco dei processi. Mentre funziona, comporta un sacco di spese generali e volevo qualcosa di più pulito.

Sapendo che avrei potuto usare un mutex per questo (ma non l'ho mai fatto prima) mi sono prefissato di ridurre il mio codice e semplificarmi la vita.

Nella classe della mia applicazione principale ho creato uno statico chiamato Mutex :

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

Avere un nome mutex ci consente di impilare la sincronizzazione su più thread e processi che è solo la magia che sto cercando.

Mutex.WaitOne ha un sovraccarico che specifica un periodo di tempo che dobbiamo attendere. Dato che in realtà non vogliamo sincronizzare il nostro codice (controlla solo se è attualmente in uso), usiamo il sovraccarico con due parametri: Mutex.WaitOne (Timeout timepan, bool exitContext) . Aspetta che uno ritorni vero se è in grado di entrare e falso se non lo fosse. In questo caso, non vogliamo affatto aspettare; Se viene utilizzato il nostro mutex, saltalo e proseguiamo, quindi passiamo in TimeSpan.Zero (attendi 0 millisecondi) e impostiamo exitContext su true in modo da poter uscire dal contesto di sincronizzazione prima di provare ad acquisire un blocco su di esso. Usando questo, avvolgiamo il nostro codice Application.Run all'interno di qualcosa del genere:

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

Quindi, se la nostra app è in esecuzione, WaitOne restituirà false e verrà visualizzata una finestra di messaggio.

Invece di mostrare una finestra di messaggio, ho scelto di utilizzare un po 'di Win32 per avvisare la mia istanza in esecuzione che qualcuno ha dimenticato che era già in esecuzione (portandosi in cima a tutte le altre finestre). Per raggiungere questo obiettivo ho usato PostMessage per trasmettere un messaggio personalizzato a tutte le finestre (il messaggio personalizzato è stato registrato con RegisterWindowMessage dalla mia applicazione in esecuzione, il che significa che solo la mia applicazione sa cosa sia) quindi la mia seconda istanza viene chiusa. L'istanza dell'applicazione in esecuzione riceverà tale notifica e la elaborerà. Per fare ciò, ho ignorato WndProc nel mio modulo principale e ho ascoltato la mia notifica personalizzata. Quando ho ricevuto quella notifica ho impostato la proprietà TopMost del modulo su true per visualizzarla in primo piano.

Ecco cosa ho finito con:

  • 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 (lato anteriore parziale)
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;
    }
}

5
Sulla base del fatto che questa risposta usa meno codice e meno librerie e fornisce il rialzo alla massima funzionalità, farò di questa la nuova risposta accettata. Se qualcuno conosce un modo più corretto per portare il modulo in alto utilizzando le API, sentiti libero di aggiungerlo.
Nidonocu,

11
@BlueRaja, avvii la prima istanza dell'app. Quando avvii la seconda istanza dell'app, rileva che un'altra istanza è già in esecuzione e si prepara all'arresto. Prima di farlo, invia un messaggio nativo "SHOWME" alla prima istanza, che porta in primo piano la prima istanza. Gli eventi in .NET non consentono la comunicazione tra processi, motivo per cui viene utilizzato il messaggio nativo.
Matt Davis,

7
C'è un modo per passare le righe di comando dall'altra istanza, forse?
gyurisc,

22
@Nam, il Mutexcostruttore richiede semplicemente una stringa, in modo da poter fornire qualsiasi nome di stringa desiderato, ad esempio "This Is My Mutex". Poiché un "Mutex" è un oggetto di sistema disponibile per altri processi, in genere si desidera che il nome sia univoco, in modo che non sia in conflitto con altri nomi "Mutex" sullo stesso sistema. Nell'articolo, la stringa dall'aspetto criptico è un "Guid". Puoi generarlo a livello di codice chiamando System.Guid.NewGuid(). Nel caso dell'articolo, probabilmente l'utente l'ha generato tramite Visual Studio come mostrato qui: msdn.microsoft.com/en-us/library/ms241442(VS.80).aspx
Matt Davis,

6
L'approccio mutex presuppone che lo stesso utente stia tentando di riavviare l'applicazione? Certamente portare "in primo piano" l'istanza esistente dell'applicazione "non ha senso dopo un" switch user "
stupido

107

Potresti usare la classe Mutex, ma scoprirai presto che dovrai implementare il codice per passare gli argomenti e simili. Bene, ho imparato un trucco durante la programmazione in WinForms quando ho letto il libro di Chris Sell . Questo trucco utilizza una logica che è già disponibile per noi nel framework. Non ti conosco, ma quando apprendo cose che posso riutilizzare nel framework, questo è di solito il percorso che faccio invece di reinventare la ruota. A meno che ovviamente non faccia tutto ciò che voglio.

Quando sono entrato in WPF, ho trovato un modo per usare lo stesso codice, ma in un'applicazione WPF. Questa soluzione dovrebbe soddisfare le tue esigenze in base alla tua domanda.

Innanzitutto, dobbiamo creare la nostra classe di applicazione. In questa classe sovrascriveremo l'evento OnStartup e creeremo un metodo chiamato Activate, che verrà usato in seguito.

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

In secondo luogo, dovremo creare una classe in grado di gestire le nostre istanze. Prima di procedere, riutilizzeremo effettivamente del codice presente nell'assembly Microsoft.VisualBasic. Da allora, in questo esempio sto usando C #, dovevo fare un riferimento all'assembly. Se stai usando VB.NET, non devi fare nulla. La classe che useremo è WindowsFormsApplicationBase ed eredita il nostro gestore di istanze da esso, quindi sfrutta le proprietà e gli eventi per gestire il singolo istancing.

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

Fondamentalmente, stiamo usando i bit VB per rilevare singole istanze ed elaborare di conseguenza. OnStartup verrà generato quando viene caricata la prima istanza. OnStartupNextInstance viene generato quando l'applicazione viene rieseguita nuovamente. Come puoi vedere, posso arrivare a ciò che è stato passato sulla riga di comando attraverso gli argomenti dell'evento. Ho impostato il valore su un campo di istanza. È possibile analizzare qui la riga di comando oppure passarla all'applicazione tramite il costruttore e la chiamata al metodo Activate.

Terzo, è tempo di creare il nostro EntryPoint. Invece di rinnovare l'applicazione come faresti normalmente, trarremo vantaggio dal nostro SingleInstanceManager.

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

Bene, spero che tu sia in grado di seguire tutto ed essere in grado di utilizzare questa implementazione e renderla tua.


9
Attaccherei con la soluzione mutex perché non ha nulla a che fare con le forme.
Steven Sudit,

1
L'ho usato perché avevo problemi con altri approcci, ma sono abbastanza sicuro che usa il telecomando sotto il cofano. La mia app ha avuto due problemi correlati: alcuni clienti dicono che tenta di telefonare a casa anche se gli hanno detto di non farlo. Quando guardano più attentamente, la connessione è localhost. Tuttavia, inizialmente non lo sanno. Inoltre, non posso usare il telecomando per uno scopo diverso (penso?) Perché è già utilizzato per questo. Quando ho provato l'approccio mutex, ho potuto utilizzare nuovamente il telecomando.
Richard Watson,

4
Perdonami, ma a meno che non mi manchi qualcosa, hai evitato di scrivere 3 righe di codice e invece hai riutilizzato il framework solo per scrivere codice piuttosto pesante per farlo. Quindi dove sono i risparmi?
Greenoldman,

2
è possibile farlo in winforms?
Jack

1
Se non si chiama InitializeComponent () sull'istanza dell'applicazione, non sarà possibile risolvere le risorse ... _application = new SingleInstanceApplication (); _application.InitializeComponent (); _application.Run ();
Nick,

84

Da qui .

Un uso comune per un Mutex tra processi è garantire che solo un'istanza di un programma possa essere eseguita alla volta. Ecco come è fatto:

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

Una buona caratteristica di Mutex è che se l'applicazione termina senza prima essere chiamato ReleaseMutex, il CLR rilascerà automaticamente Mutex.


5
Devo dire che mi piace questa risposta molto più di quella accettata semplicemente perché non dipende da WinForms. Personalmente la maggior parte del mio sviluppo si è spostata su WPF e non voglio inserire le librerie WinForm per qualcosa del genere.
Switters,

5
Naturalmente, per essere una risposta completa, devi anche descrivere passare gli argomenti all'altra istanza :)
Simon Buchan,

@Jason, bene, grazie! Ma preferisco non passare alcun timeout. È molto soggettivo e dipende da così tante variabili. Se vuoi abilitare l'avvio di un'altra app, rilascia il tuo mutex più velocemente .. per esempio non appena l'utente conferma di chiudere
Eric Ouellet,

@EricOuellet: praticamente ogni programma che ha le schede fa questo: Photoshop, Sublime Text, Chrome .... Se hai una buona ragione per avere un processo "master" (supponi di avere un DB in-proc per le impostazioni) potresti voglio che mostri l'interfaccia utente come se fosse anche un nuovo processo.
Simon Buchan,

@Simon, hai ragione. Mi chiedo solo una cosa molto vecchia ... MDI vs SDI (Interfaccia multi-documento vs Interfaccia singolo documento). Quando parli di schede, ti riferisci a MDI. Nel 1998, un libro di Microsoft suggerisce di eliminare ogni app MDI. Microsoft ha cambiato Word, Excel ... in SDI, che penso sia più semplice e migliore. Capisco che Chrome e altri (ora IE) vogliono tornare a MDI. Personalmente (sulla base di nulla / sentimenti personali) che è ancora meglio aprire una nuova app quando si seleziona l'associazione file. Ma capisco meglio la domanda posta ora. Grazie !
Eric Ouellet,

58

MSDN ha effettivamente un'applicazione di esempio per C # e VB per fare esattamente questo: http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx

La tecnica più comune e affidabile per lo sviluppo del rilevamento a istanza singola è l'utilizzo dell'infrastruttura remota di Microsoft .NET Framework (System.Remoting). Microsoft .NET Framework (versione 2.0) include un tipo, WindowsFormsApplicationBase, che incapsula la funzionalità di remoting richiesta. Per incorporare questo tipo in un'applicazione WPF, un tipo deve derivare da esso ed essere utilizzato come shim tra il metodo del punto di ingresso statico dell'applicazione, Main e il tipo di applicazione dell'applicazione WPF. Lo shim rileva quando viene lanciata un'applicazione per la prima volta e quando vengono tentati i lanci successivi e i rendimenti controllano il tipo di applicazione WPF per determinare come elaborare i lanci.

  • Per C # le persone fanno solo un respiro profondo e dimenticano del tutto "Non voglio includere VisualBasic DLL". Per questo e per quello che dice Scott Hanselman e per il fatto che questa è praticamente la soluzione più pulita al problema ed è progettata da persone che sanno molto di più sul framework di te.
  • Dal punto di vista dell'usabilità, il fatto è che il tuo utente sta caricando un'applicazione ed è già aperta e stai dando loro un messaggio di errore come 'Another instance of the app is running. Bye'se non fossero un utente molto felice. Devi semplicemente (in un'applicazione GUI) passare a quell'applicazione e passare gli argomenti forniti - o se i parametri della riga di comando non hanno alcun significato, devi far apparire l'applicazione che potrebbe essere stata minimizzata.

Il framework ha già il supporto per questo - è solo che qualche idiota ha chiamato la DLL Microsoft.VisualBasice non è stato inseritoMicrosoft.ApplicationUtils qualcosa del genere. Superalo - o apri Reflector.

Suggerimento: se usi questo approccio esattamente come è, e hai già un App.xaml con risorse ecc., Ti consigliamo di dare un'occhiata anche a questo .


Grazie per aver incluso il link 'Dai un'occhiata anche a questo'. Questo è esattamente ciò di cui avevo bisogno. A proposito, la soluzione n. 3 nel tuo link è la migliore.
Eterno

Sono anche sostenitore della delega al framework e alle librerie appositamente progettate, ove possibile.
Eniola,

23

Questo codice dovrebbe andare al metodo principale. Guarda qui per maggiori informazioni sul metodo principale 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; 
    }
}

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

Nota: i metodi sopra riportati presuppongono che il processo / l'applicazione abbia un nome univoco. Perché utilizza il nome del processo per trovare eventuali processori esistenti. Quindi, se la tua applicazione ha un nome molto comune (ad esempio: Blocco note), l'approccio sopra non funzionerà.


1
Inoltre, questo non funzionerà se sul tuo computer è in esecuzione un altro programma con lo stesso nome. ProcessNamerestituisce il nome del file eseguibile meno il exe. Se crei un'applicazione chiamata "Blocco note" e il blocco note di Windows è in esecuzione, lo rileverà mentre l'applicazione è in esecuzione.
Jcl

1
Grazie per questa risposta Ho trovato così tante domande simili e le risposte sono sempre state così elaborate e / o confuse che le ho trovate inutili. Questo (metodo n. 1) è semplice, chiaro e soprattutto mi ha aiutato a far funzionare il mio codice.
ElDoRado1239,

20

Bene, ho una classe usa e getta per questo che funziona facilmente per la maggior parte dei casi d'uso:

Usalo in questo modo:

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

        // Initialize program here.
    }
}

Ecco qui:

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

1
questo è stato abbastanza facile da far funzionare. Non chiuderebbe la seconda applicazione finché non avessi modificato Application.Exit (); ad un semplice ritorno; ma a parte questo è fantastico. Anche se lo ammetto, esaminerò più da vicino la soluzione precedente poiché utilizza un'interfaccia. blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/…
hal9000

15

Una nuova che utilizza roba Mutex e IPC e passa anche qualsiasi argomento della riga di comando all'istanza in esecuzione, è WPF Single Instance Application .


Lo uso con grande successo. Se si incorporano NamedPipes con questo, è anche possibile passare argomenti della riga di comando all'applicazione originale. La classe "SingleInstance.cs" è stata scritta da Microsoft. Ho aggiunto un altro link a una versione più leggibile del blog di Arik Poznanski su CodeProject.
Heliac,

1
Il collegamento ora è interrotto.
Mike Lowery,

11

Il codice C # .NET Single Instance Application che è il riferimento per la risposta contrassegnata è un ottimo inizio.

Tuttavia, ho scoperto che non gestisce molto bene i casi in cui l'istanza già esistente ha una finestra di dialogo modale aperta, indipendentemente dal fatto che quella finestra di dialogo sia gestita (come un altro Modulo come un riquadro informazioni) o non gestita (come il OpenFileDialog anche quando si utilizza la classe .NET standard). Con il codice originale, viene attivato il modulo principale, ma quello modale rimane inattivo, il che sembra strano, inoltre l'utente deve fare clic su di esso per continuare a utilizzare l'app.

Quindi, ho creato una classe di utilità SingleInstance per gestire tutto ciò in modo abbastanza automatico per le applicazioni Winforms e WPF.

Winforms :

1) modificare la classe del programma in questo modo:

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) modificare la classe della finestra principale in questo modo:

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) modifica la pagina dell'app in questo modo (e assicurati di impostare l'azione di costruzione su page per poter ridefinire il metodo principale):

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) modificare la classe della finestra principale in questo modo:

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

Ed ecco la classe di 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);
        }
    }
}

10

Ecco un esempio che ti consente di avere una singola istanza di un'applicazione. Quando vengono caricate nuove istanze, passano i loro argomenti all'istanza principale in esecuzione.

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

Questo è davvero un bell'esempio di cosa devo fare. Nathan, tutti gli argomenti inviati vengono inviati con questo metodo? Ne ho circa 7 nella mia app e penso che questo codice funzionerà.
kevp,

1
Nel mio esempio, viene inviato solo il primo argomento, ma può essere modificato in modo che vengano inviati tutti.
Nathan Moinvaziri,

8

Solo alcuni pensieri: ci sono casi in cui è richiesto che solo un'istanza di un'applicazione non sia "zoppa" come alcuni vorrebbero farti credere. Le app di database, ecc. Sono un ordine di grandezza più difficile se si consente a più istanze dell'app per un singolo utente di accedere a un database (sai, tutto ciò che aggiorna tutti gli archivi aperti in più istanze dell'app sugli utenti macchina, ecc.). In primo luogo, per la "cosa della collisione del nome, non usare un nome leggibile dall'uomo - usa invece un GUID o, meglio ancora un GUID + il nome leggibile dall'uomo. Le possibilità di collisione del nome sono appena scese dal radar e al Mutex non importa Come qualcuno ha sottolineato, un attacco DOS farebbe schifo, ma se la persona maliziosa ha avuto il problema di ottenere il nome mutex e incorporarlo nella loro app, sei praticamente un bersaglio comunque e dovrai fare MOLTO di più per proteggerti di un semplice nome di mutex. Inoltre, se si utilizza la variante di: new Mutex (true, "alcuni GUID più nome", out AIsFirstInstance), si ha già l'indicatore del fatto che il Mutex sia o meno la prima istanza.


6

Così tante risposte a una domanda così apparentemente semplice. Solo per scuotere un po 'le cose qui è la mia soluzione a questo problema.

La creazione di un Mutex può essere problematica perché JIT-er ti vede solo usarlo per una piccola parte del codice e vuole contrassegnarlo come pronto per la garbage collection. Praticamente vuole essere più intelligente di te pensando che non userai quel Mutex per così tanto tempo. In realtà, vuoi aggrapparti a questo Mutex per tutto il tempo in cui l'applicazione è in esecuzione. Il modo migliore per dire al garbage collector di lasciarti solo Mutex è di dirlo per tenerlo in vita attraverso le diverse generazioni di garage. Esempio:

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

Ho sollevato l'idea da questa pagina: http://www.ai.uga.edu/~mc/SingleInstance.html


3
Non sarebbe più facile archiviarne una copia condivisa nella classe dell'applicazione?
Rossisdead,

6

Sembra che ci sia un ottimo modo per gestirlo:

Applicazione WPF a istanza singola

Questo fornisce una classe che puoi aggiungere che gestisce tutto il mutex e il cruff di messaggistica per semplificare la tua implementazione al punto in cui è semplicemente banale.


Questo non ha portato in primo piano la finestra esistente quando l'ho provata.
RandomEngy,

6

Il codice seguente è la mia soluzione di pipe denominata WCF per registrare un'applicazione a istanza singola. È bello perché genera anche un evento quando un'altra istanza tenta di avviarsi e riceve la riga di comando dell'altra istanza.

È orientato verso WPF perché utilizza la System.Windows.StartupEventHandlerclasse, ma potrebbe essere facilmente modificato.

Questo codice richiede un riferimento a PresentationFrameworke System.ServiceModel.

Uso:

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

Codice sorgente:

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

5

Non utilizzare mai un nome mutex per implementare un'applicazione a istanza singola (o almeno non per il codice di produzione). Il codice dannoso può facilmente fare ( Denial of Service ) il tuo culo ...


8
"Non dovresti mai usare un nome mutex" - non dire mai mai. Se sul mio computer è in esecuzione un codice dannoso, probabilmente sono già in difficoltà.
Joe,

In realtà non deve nemmeno essere un codice dannoso. Potrebbe essere solo una collisione accidentale di nomi.
Matt Davison,

Allora cosa dovresti fare?
Kevin Berridge,

La domanda migliore è quale possibile ragione vorresti quel comportamento. Non progettare la tua app come applicazione a istanza singola =). So che è una risposta scadente, ma dal punto di vista del design è quasi sempre la risposta corretta. Senza sapere di più sull'app è difficile dire molto di più.
Matt Davison,

2
Almeno sotto Windows, i mutex hanno il controllo dell'accesso, quindi uno può giocare con il tuo oggetto. Per quanto riguarda il nome delle collisioni, ecco perché sono stati inventati UUID / GUID.
NuSkooler,

5

Guarda il codice seguente. È una soluzione eccezionale e semplice per prevenire più istanze di un'applicazione 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();
    }
}

4

Ecco cosa uso. Ha combinato l'enumerazione dei processi per eseguire il cambio e mutex per proteggersi dai "clicker attivi":

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

4

Ho trovato la soluzione più semplice, simile a quella di Dale Ragan, ma leggermente modificata. Fa praticamente tutto ciò di cui hai bisogno e basato sulla classe standard Microsoft WindowsFormsApplicationBase.

Innanzitutto, crei la classe SingleInstanceController, che puoi utilizzare in tutte le altre applicazioni a istanza singola, che utilizzano Windows Form:

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

Quindi puoi usarlo nel tuo programma come segue:

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

Sia il programma che la soluzione SingleInstanceController_NET ​​devono fare riferimento a Microsoft.VisualBasic. Se si desidera solo riattivare l'applicazione in esecuzione come una finestra normale quando l'utente tenta di riavviare il programma in esecuzione, il secondo parametro in SingleInstanceController può essere nullo. Nell'esempio dato, la finestra è ingrandita.


4

Aggiornamento 25/01/2017. Dopo aver provato alcune cose, ho deciso di utilizzare VisualBasic.dll, è più semplice e funziona meglio (almeno per me). Ho lasciato la mia risposta precedente solo come riferimento ...

Proprio come riferimento, è così che ho fatto senza passare argomenti (che non riesco a trovare alcun motivo per farlo ... intendo una singola app con argomenti che devono essere passati da un'istanza all'altra). Se è richiesta l'associazione dei file, un'app dovrebbe (per aspettativa standard degli utenti) essere istanziata per ogni documento. Se dovessi passare args all'app esistente, penso che userei vb dll.

Non passando args (solo app a singola istanza), preferisco non registrare un nuovo messaggio di Windows e non sovrascrivere il ciclo di messaggi come definito nella soluzione Matt Davis. Anche se non è un grosso problema aggiungere una dll VisualBasic, ma preferisco non aggiungere un nuovo riferimento solo per fare un'app a singola istanza. Inoltre, preferisco istanziare una nuova classe con Main anziché chiamare Shutdown dall'override di App.Startup per assicurarti di uscire il prima possibile.

Nella speranza che piacerà a chiunque ... o ispirerà un po ':-)

La classe di avvio del progetto deve essere impostata come '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);

3

Non usando Mutex, risposta semplice:

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

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

Mettilo nel Program.Main().
Esempio :

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

È possibile aggiungere MessageBox.Showalla ifdichiarazione e inserire "Applicazione già in esecuzione".
Questo potrebbe essere utile a qualcuno.


4
Se due processi iniziano contemporaneamente, entrambi possono vedere due processi attivi e terminarsi automaticamente.
AL

@AT Sì, questo può essere utile anche per le app in esecuzione come amministratore oppure
newbieguy

Se si crea una copia dell'applicazione e la si rinomina, è possibile eseguire l'originale e la copia contemporaneamente.
Dominique Bijnens,

2

Gli approcci basati sul nome mutex non sono multipiattaforma perché i mutex nominati non sono globali in Mono. Gli approcci basati sull'enumerazione dei processi non hanno alcuna sincronizzazione e possono comportare comportamenti errati (ad es. Più processi avviati contemporaneamente possono terminare automaticamente in base alla tempistica). Approcci basati su sistemi a finestre non sono desiderabili in un'applicazione console. Questa soluzione, basata sulla risposta di Divin, risolve tutti questi problemi:

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

2

Uso Mutex nella mia soluzione per prevenire più istanze.

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

1

Usa la soluzione 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;
            }
        }
    }
}
}

1

Ecco una soluzione leggera che uso che consente all'applicazione di portare in primo piano una finestra già esistente senza ricorrere a messaggi personalizzati di Windows o alla ricerca cieca di nomi di processi.

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

Modifica: puoi anche archiviare e inizializzare mutex e create New in modo statico, ma al termine dovrai eliminare / rilasciare esplicitamente il mutex. Personalmente, preferisco mantenere il mutex locale in quanto verrà automaticamente eliminato anche se l'applicazione si chiude senza mai raggiungere la fine di Main.



1

Ho aggiunto un metodo sendMessage alla classe NativeMethods.

Apparentemente il dosent del metodo postmessage funziona, se l'applicazione non viene visualizzata nella barra delle applicazioni, tuttavia l'utilizzo del metodo sendmessage risolve questo problema.

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

1

Ecco la stessa cosa implementata tramite 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();
    }
}

1

[Di seguito ho fornito un codice di esempio per le applicazioni console e wpf.]

Devi solo controllare il valore della createdNewvariabile (esempio sotto!), Dopo aver creato l'istanza Mutex denominata.

Il booleano createdNewrestituirà false:

se l'istanza Mutex denominata "YourApplicationNameHere" era già stata creata sul sistema da qualche parte

Il booleano createdNewtornerà vero:

se questo è il primo Mutex chiamato "YourApplicationNameHere" sul sistema.


Applicazione console - Esempio:

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 Esempio:

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

1

Una soluzione per risparmiare tempo per Winforms C # ...

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

1

Verificare da qui la soluzione proposta che utilizza un semaforo per determinare se un'istanza esistente è già in esecuzione, funziona per un'applicazione WPF e può passare argomenti dalla seconda istanza alla prima istanza già in esecuzione utilizzando un TcpListener e un TcpClient:

Funziona anche per .NET Core, non solo per .NET Framework.


1

Non riesco a trovare una soluzione breve qui quindi spero che a qualcuno piacerà questo:

AGGIORNATO 2018-09-20

Inserisci questo codice nel tuo 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());
}

Ciò introdurrà una condizione di gara. Deve usare un mutex.
Georgiosd,

1
non è garantito che se si eseguono due istanze contemporaneamente, ciò funzionerà. Come aggiornare una variabile da due thread diversi. Affari rischiosi ingannevoli. Usa la forza, Luca :)
georgiosd,

@georgiosd ah capisco cosa intendi. Come se qualcuno avviasse .exe e cambiasse il nome. Sì, questo sarebbe un modo per avviarlo più volte, ma normalmente .exe non funziona, se il nome fosse cambiato. Aggiornerò la mia risposta ^^ Grazie Luke: D per averlo sottolineato :)
Deniz

1
Non solo @Deniz. Se avvii due processi molto velocemente, c'è la possibilità che l'elenco dei processi o il metodo che li recupera vengano eseguiti mentre è ancora visualizzato uno solo. Questo potrebbe essere un caso marginale per te irrilevante, ma questa è una domanda generale ...
georgiosd,

@georgiosd Puoi dimostrarlo? Perché Iv'e l'ha provato solo per te hehe. Ma non è stato possibile per me, anche davvero "molto veloce"! : P Quindi non capisco perché credi in qualcosa che non è proprio il caso e non ti piace nemmeno questo codice innocente: D
Deniz,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.