¿Cuál es la forma correcta de crear una aplicación WPF de instancia única?

657

Usando C # y WPF en .NET (en lugar de Windows Forms o consola), ¿cuál es la forma correcta de crear una aplicación que solo se puede ejecutar como una instancia única?

Sé que tiene algo que ver con algo mítico llamado mutex, rara vez puedo encontrar a alguien que se moleste en detenerse y explicar cuáles son.

El código también debe informar a la instancia que ya se está ejecutando que el usuario intentó iniciar una segunda, y tal vez también pasar cualquier argumento de la línea de comandos si existiera.

Nidonocu
fuente
14
¿El CLR no libera automáticamente mutexes inéditos cuando la aplicación finaliza de todos modos?
Cocowalla
1
@ Cocowalla: el finalizador debe deshacerse de los mutex no administrados a menos que no pueda saber si el mutex fue creado por la aplicación administrada o adjuntada a una existente.
Ignacio Soler García
Tener una sola instancia de su aplicación es razonable. Pero pasar argumentos a una aplicación ya existente me parece un poco tonto. No veo ninguna razón para hacerlo. Si asocia una aplicación con extensión de archivo, debe abrir tantas aplicaciones como el usuario desee abrir documentos. Ese es el comportamiento estándar que todos los usuarios esperarían.
Eric Ouellet
99
@Cocowalla El CLR no gestiona recursos nativos. Sin embargo, si un proceso finaliza, el sistema libera todos los identificadores (el sistema operativo, no el CLR).
Inspectable el
1
Prefiero la respuesta de @huseyint. Utiliza la propia clase 'SingleInstance.cs' de Microsoft, por lo que no tiene que preocuparse por Mutexes e IntPtrs. Además, no depende de VisualBasic (yuk). Ver codereview.stackexchange.com/questions/20871/… para más ...
Heliac

Respuestas:

537

Aquí hay un muy buen artículo sobre la solución Mutex. El enfoque descrito por el artículo es ventajoso por dos razones.

Primero, no requiere una dependencia en el ensamblado Microsoft.VisualBasic. Si mi proyecto ya dependiera de ese ensamblado, probablemente recomendaría usar el enfoque que se muestra en otra respuesta . Pero como es, no uso el ensamblado Microsoft.VisualBasic, y prefiero no agregar una dependencia innecesaria a mi proyecto.

En segundo lugar, el artículo muestra cómo poner en primer plano la instancia existente de la aplicación cuando el usuario intenta iniciar otra instancia. Es un toque muy agradable que las otras soluciones Mutex descritas aquí no abordan.


ACTUALIZAR

A partir del 8/1/2014, el artículo que he vinculado anteriormente aún está activo, pero el blog no se ha actualizado desde hace tiempo. Eso me preocupa de que eventualmente pueda desaparecer, y con ello, la solución recomendada. Estoy reproduciendo el contenido del artículo aquí para la posteridad. Las palabras pertenecen únicamente al propietario del blog en Sanity Free Coding .

Hoy quería refactorizar un código que prohibía que mi aplicación ejecutara varias instancias de sí misma.

Anteriormente había usado System.Diagnostics.Process para buscar una instancia de mi myapp.exe en la lista de procesos. Si bien esto funciona, genera muchos gastos generales, y quería algo más limpio.

Sabiendo que podía usar un mutex para esto (pero nunca antes lo había hecho), me propuse cortar mi código y simplificar mi vida.

En la clase de mi aplicación principal, creé una estática llamada Mutex :

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

Tener un mutex con nombre nos permite apilar la sincronización en múltiples subprocesos y procesos, que es la magia que estoy buscando.

Mutex.WaitOne tiene una sobrecarga que especifica una cantidad de tiempo para que esperemos. Como en realidad no queremos sincronizar nuestro código (más solo verifique si está actualmente en uso), usamos la sobrecarga con dos parámetros: Mutex.WaitOne (tiempo de espera, bool exitContext) . Espere uno devuelve verdadero si puede ingresar, y falso si no lo fue. En este caso, no queremos esperar en absoluto; Si se utiliza nuestro mutex, omítalo y continúe, de modo que pasamos TimeSpan.Zero (espere 0 milisegundos) y establezca exitContext en true para que podamos salir del contexto de sincronización antes de intentar obtener un bloqueo. Con esto, envolvemos nuestro código Application.Run dentro de algo como esto:

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

Entonces, si nuestra aplicación se está ejecutando, WaitOne devolverá false, y obtendremos un cuadro de mensaje.

En lugar de mostrar un cuadro de mensaje, opté por utilizar un pequeño Win32 para notificar a mi instancia en ejecución que alguien olvidó que ya se estaba ejecutando (al ubicarse en la parte superior de todas las demás ventanas). Para lograr esto, utilicé PostMessage para transmitir un mensaje personalizado a cada ventana (el mensaje personalizado fue registrado con RegisterWindowMessage por mi aplicación en ejecución, lo que significa que solo mi aplicación sabe lo que es) y luego sale mi segunda instancia. La instancia de la aplicación en ejecución recibiría esa notificación y la procesaría. Para hacer eso, anulé WndProc en mi formulario principal y escuché mi notificación personalizada. Cuando recibí esa notificación, configuré la propiedad TopMost del formulario en true para que aparezca en la parte superior.

Esto es lo que terminé 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 (parte frontal parcial)
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
fuente
55
Sobre la base de que esta respuesta usa menos código y menos bibliotecas y proporciona la funcionalidad de subir a la cima, voy a hacer de esta la nueva respuesta aceptada. Si alguien conoce una forma más correcta de llevar el formulario a la cima utilizando las API, no dude en agregarlo.
Nidonocu
11
@BlueRaja, inicias la primera instancia de la aplicación. Cuando inicia la segunda instancia de la aplicación, detecta que ya se está ejecutando otra instancia y se prepara para cerrar. Antes de hacerlo, envía un mensaje nativo "SHOWME" a la primera instancia, que lleva la primera instancia a la parte superior. Los eventos en .NET no permiten la comunicación entre procesos, por lo que se utiliza el mensaje nativo.
Matt Davis
77
¿Hay alguna manera de pasar las líneas de comando de la otra instancia, tal vez?
gyurisc 01 de
22
@Nam, el Mutexconstructor simplemente requiere una cadena, por lo que puede proporcionar cualquier nombre de cadena que desee, por ejemplo, "This Is My Mutex". Debido a que un 'Mutex' es un objeto del sistema que está disponible para otros procesos, normalmente desea que el nombre sea único para que no choque con otros nombres 'Mutex' en el mismo sistema. En el artículo, la cadena de aspecto críptico es un 'Guid'. Puede generar esto mediante programación llamando System.Guid.NewGuid(). En el caso del artículo, el usuario probablemente lo generó a través de Visual Studio como se muestra aquí: msdn.microsoft.com/en-us/library/ms241442(VS.80).aspx
Matt Davis
66
¿El enfoque mutex supone que el mismo usuario está intentando iniciar la aplicación nuevamente? Ciertamente, poner "la instancia existente de la aplicación en primer plano" no tiene sentido después de un "cambio de usuario"
dumbledad
107

Podría usar la clase Mutex, pero pronto descubrirá que necesitará implementar el código para pasar los argumentos y usted mismo. Bueno, aprendí un truco al programar en WinForms cuando leí el libro de Chris Sell . Este truco usa lógica que ya está disponible para nosotros en el marco. No sé sobre ti, pero cuando aprendo sobre cosas que puedo reutilizar en el marco, esa suele ser la ruta que tomo en lugar de reinventar la rueda. A menos que, por supuesto, no haga todo lo que quiero.

Cuando ingresé a WPF, se me ocurrió una manera de usar el mismo código, pero en una aplicación WPF. Esta solución debe satisfacer sus necesidades en función de su pregunta.

Primero, necesitamos crear nuestra clase de aplicación. En esta clase vamos a anular el evento OnStartup y crear un método llamado Activate, que se usará más adelante.

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

En segundo lugar, necesitaremos crear una clase que pueda administrar nuestras instancias. Antes de pasar por eso, en realidad vamos a reutilizar parte del código que se encuentra en el ensamblado Microsoft.VisualBasic. Como estoy usando C # en este ejemplo, tuve que hacer una referencia al ensamblaje. Si está utilizando VB.NET, no tiene que hacer nada. La clase que vamos a usar es WindowsFormsApplicationBase y heredará nuestro administrador de instancias y luego aprovechará las propiedades y eventos para manejar la instancia única.

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

Básicamente, estamos utilizando los bits VB para detectar instancias únicas y procesarlas en consecuencia. OnStartup se activará cuando se cargue la primera instancia. OnStartupNextInstance se activa cuando la aplicación se vuelve a ejecutar. Como puede ver, puedo llegar a lo que se pasó en la línea de comando a través de los argumentos del evento. Establecí el valor en un campo de instancia. Puede analizar la línea de comando aquí, o puede pasarla a su aplicación a través del constructor y la llamada al método Activate.

Tercero, es hora de crear nuestro EntryPoint. En lugar de actualizar la aplicación como lo haría normalmente, vamos a aprovechar nuestro SingleInstanceManager.

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

Bueno, espero que puedas seguir todo y poder usar esta implementación y hacerla tuya.

Dale Ragan
fuente
99
Me quedaría con la solución mutex porque no tiene nada que ver con los formularios.
Steven Sudit
1
Lo he usado porque tuve problemas con otros enfoques, pero estoy bastante seguro de que usa el control remoto bajo el capó. Mi aplicación ha tenido dos problemas relacionados: algunos clientes dicen que intenta llamar a casa a pesar de que le han dicho que no lo haga. Cuando miran más cuidadosamente, la conexión es con localhost. Aún así, inicialmente no lo saben. Además, no puedo usar el control remoto para un propósito diferente (¿creo?) Porque ya se está usando para esto. Cuando probé el enfoque mutex, pude volver a usar la comunicación remota.
Richard Watson el
44
Perdóname, pero a menos que me falte algo, evitaste escribir 3 líneas de código y en su lugar reutilizaste el framework solo para escribir un código bastante pesado para hacerlo. Entonces, ¿dónde están los ahorros?
greenoldman
2
es posible hacerlo en winforms?
Jack
1
Si no llama a InitializeComponent () en la instancia de la aplicación, no podrá resolver los recursos ... _application = new SingleInstanceApplication (); _application.InitializeComponent (); _application.Run ();
Nick
84

A partir de aquí .

Un uso común para un Mutex entre procesos es garantizar que solo se pueda ejecutar una instancia de un programa a la vez. Así es como se hace:

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 buena característica de Mutex es que si la aplicación termina sin que se llame primero a ReleaseMutex, el CLR liberará el Mutex automáticamente.

jason saldo
fuente
55
Debo decir que esta respuesta me gusta mucho más que la aceptada simplemente porque no depende de WinForms. Personalmente, la mayor parte de mi desarrollo se ha trasladado a WPF y no quiero tener que recurrir a las bibliotecas de WinForm para algo como esto.
Interruptores
55
Por supuesto, para ser una respuesta completa, también debe describir pasar los argumentos a la otra instancia :)
Simon Buchan
@ Jason, bien, gracias! Pero prefiero no pasar ningún tiempo de espera. Es muy subjetivo y depende de muchas variables. Si alguna vez desea que se inicie otra aplicación, simplemente libere su mutex más rápido ... por ejemplo, tan pronto como el usuario confirme el cierre
Eric Ouellet
@EricOuellet: casi todos los programas que tienen pestañas hacen esto: Photoshop, Sublime Text, Chrome ... Si tiene una buena razón para tener un proceso "maestro" (digamos que tiene una base de datos en proceso para la configuración) puede quiero que muestre la IU como si fuera un proceso nuevo también.
Simon Buchan
@ Simon, tienes razón. Solo me pregunto sobre una cosa muy antigua ... MDI vs SDI (Interfaz de documentos múltiples versus interfaz de documento único). Cuando habla de pestañas, se refiere a MDI. En 1998, un libro de Microsoft sugiere eliminar todas las aplicaciones MDI. Microsoft cambió Word, Excel ... a SDI, que creo que es más simple y mejor. Entiendo que Chrome y otros (ahora IE) quieren volver a MDI. Personalmente (basado en nada / sentimientos personales) que todavía es mejor abrir una nueva aplicación cuando se selecciona la asociación de archivos. Pero entiendo mejor la pregunta que se hace ahora. Gracias !
Eric Ouellet
58

MSDN en realidad tiene una aplicación de muestra tanto para C # como para VB para hacer exactamente esto: http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx

La técnica más común y confiable para desarrollar detección de instancia única es usar la infraestructura remota de Microsoft .NET Framework (System.Remoting). Microsoft .NET Framework (versión 2.0) incluye un tipo, WindowsFormsApplicationBase, que encapsula la funcionalidad de comunicación remota requerida. Para incorporar este tipo en una aplicación WPF, un tipo debe derivarse de él y ser usado como un calce entre el método de punto de entrada estático de la aplicación, Main, y el tipo de aplicación de la aplicación WPF. La laminilla detecta cuándo se inicia una aplicación por primera vez y cuándo se intentan lanzamientos posteriores, y cede el control del tipo de aplicación WPF para determinar cómo procesar los lanzamientos.

  • Para C # las personas simplemente respiran hondo y se olvidan de todo el 'No quiero incluir VisualBasic DLL'. Por esto y por lo que dice Scott Hanselman y al hecho de que esta es la solución más limpia para el problema y está diseñada por personas que saben mucho más sobre el marco que tú.
  • Desde el punto de vista de la usabilidad, el hecho es que si su usuario está cargando una aplicación y ya está abierta y le está dando un mensaje de error, 'Another instance of the app is running. Bye'entonces no será un usuario muy feliz. Simplemente DEBE (en una aplicación GUI) cambiar a esa aplicación y pasar los argumentos proporcionados, o si los parámetros de la línea de comandos no tienen significado, entonces debe abrir la aplicación que puede haber sido minimizada.

El marco ya tiene soporte para esto, es solo que un idiota llamó a la DLL Microsoft.VisualBasicy no se puso enMicrosoft.ApplicationUtils o algo así. Supéralo, o abre Reflector.

Consejo: Si usa este enfoque exactamente como está, y ya tiene un App.xaml con recursos, etc., también querrá echarle un vistazo .

Simon_Weaver
fuente
Gracias por incluir el enlace "mira esto también". Eso es exactamente lo que necesitaba. Por cierto, la solución # 3 en su enlace es la mejor.
Eternal21
También soy un defensor de la delegación en el marco y las bibliotecas especialmente diseñadas cuando sea posible.
Eniola
23

Este código debe ir al método principal. Mire aquí para obtener más información sobre el método principal en WPF.

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

private const int SW_SHOWMAXIMIZED = 3;

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

Método 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: Los métodos anteriores suponen que su proceso / aplicación tiene un nombre único. Porque utiliza el nombre del proceso para buscar si hay procesadores existentes. Entonces, si su aplicación tiene un nombre muy común (es decir: Bloc de notas), el enfoque anterior no funcionará.

CharithJ
fuente
1
Además, esto no funcionará si hay algún otro programa ejecutándose en su computadora con el mismo nombre. ProcessNamedevuelve el nombre del archivo ejecutable menos el exe. Si crea una aplicación llamada "Bloc de notas" y el bloc de notas de Windows se está ejecutando, lo detectará mientras se ejecuta su aplicación.
Jcl
1
Gracias por esta respuesta He encontrado muchas preguntas similares y las respuestas siempre fueron tan elaboradas y / o confusas que las he encontrado inútiles. Este (Método # 1) es sencillo, claro y, sobre todo, me ayudó a ejecutar mi código.
ElDoRado1239
20

Bueno, tengo una clase desechable para esto que funciona fácilmente para la mayoría de los casos de uso:

Úselo así:

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

        // Initialize program here.
    }
}

Aquí está:

/// <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
fuente
1
Este fue bastante fácil de conseguir. No cerraría la segunda aplicación hasta que haya cambiado Application.Exit (); a un simple retorno; pero aparte de eso es genial. Aunque admito que voy a ver más de cerca la solución anterior, ya que utiliza una interfaz. blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/…
hal9000
15

Una nueva aplicación que usa material Mutex e IPC, y también pasa cualquier argumento de línea de comando a la instancia en ejecución, es la Aplicación de instancia única de WPF .

huseyint
fuente
Lo uso con gran éxito. Si incorpora NamedPipes con esto, también puede pasar argumentos de línea de comandos a la aplicación original. La clase, 'SingleInstance.cs', fue escrita por Microsoft. He agregado otro enlace a una versión más legible del blog de Arik Poznanski en CodeProject.
Heliac
1
El enlace ahora está roto.
Mike Lowery
11

El código C # .NET Aplicación de instancia única que es la referencia para la respuesta marcada es un gran comienzo.

Sin embargo, descubrí que no maneja muy bien los casos en que la instancia que ya existe tiene un cuadro de diálogo modal abierto, ya sea que ese cuadro de diálogo sea administrado (como otro Formulario, como un cuadro sobre), o no administrado (como el OpenFileDialog incluso cuando se usa la clase estándar .NET). Con el código original, se activa el formulario principal, pero el modal permanece inactivo, lo que parece extraño, además el usuario debe hacer clic en él para seguir usando la aplicación.

Por lo tanto, he creado una clase de utilidad SingleInstance para manejar todo esto de forma bastante automática para Winforms y aplicaciones WPF.

Winforms :

1) modifique la clase del programa de esta manera:

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) modifique la clase de ventana principal de esta manera:

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) modifique la página de la aplicación de esta manera (y asegúrese de establecer su acción de compilación en la página para poder redefinir el método Principal):

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) modifique la clase de ventana principal de esta manera:

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

Y aquí está la clase de utilidad:

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

Aquí hay un ejemplo que le permite tener una sola instancia de una aplicación. Cuando se cargan nuevas instancias, pasan sus argumentos a la instancia principal que se está ejecutando.

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
fuente
Este es un muy buen ejemplo de lo que debo hacer. Nathan, ¿se envían todos los argumentos usando este método? Tengo aproximadamente 7 en mi aplicación y creo que este código funcionará.
kevp
1
En mi ejemplo, solo se envía el primer argumento, pero se puede cambiar para que se envíen todos.
Nathan Moinvaziri
8

Solo algunas reflexiones: hay casos en los que se requiere que solo una instancia de una aplicación no sea "poco convincente", como algunos lo harían creer. Las aplicaciones de bases de datos, etc. son un orden de magnitud más difícil si uno permite que múltiples instancias de la aplicación accedan a una base de datos para un solo usuario (ya sabes, todo eso actualiza todos los registros que están abiertos en múltiples instancias de la aplicación para los usuarios máquina, etc.). Primero, para la "colisión de nombres", no use un nombre legible por humanos - use un GUID en su lugar o, mejor aún, un GUID + el nombre legible por humanos. Las posibilidades de colisión de nombres simplemente desaparecieron del radar y al Mutex no le importa Como alguien señaló, un ataque de DOS sería un asco, pero si la persona maliciosa se ha tomado la molestia de obtener el nombre de mutex e incorporarlo a su aplicación, de todos modos, eres prácticamente un objetivo y tendrás que hacer MUCHO más para protegerte que solo manipular un nombre mutex. Además, si uno usa la variante de: nuevo Mutex (verdadero, "algún GUID más Nombre", fuera AIsFirstInstance), ya tiene su indicador de si el Mutex es o no la primera instancia.

Bruce
fuente
6

Tantas respuestas a una pregunta aparentemente tan simple. Solo para sacudir un poco las cosas aquí es mi solución a este problema.

Crear un Mutex puede ser problemático porque el JIT-er solo lo ve usándolo para una pequeña porción de su código y quiere marcarlo como listo para la recolección de basura. Quiere ser más inteligente que pensar que no va a utilizar ese Mutex durante tanto tiempo. En realidad, desea aferrarse a este Mutex mientras su aplicación se esté ejecutando. La mejor manera de decirle al recolector de basura que te deje solo Mutex es decirle que lo mantenga vivo a través de las diferentes generaciones de recolección en el garaje. Ejemplo:

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

Levanté la idea de esta página: http://www.ai.uga.edu/~mc/SingleInstance.html

Peter
fuente
3
¿No sería más fácil almacenar una copia compartida en la clase de aplicación?
rossisdead
6

Parece que hay una muy buena manera de manejar esto:

Aplicación de instancia única de WPF

Esto proporciona una clase que puede agregar que administra todos los mutex y mensajes crujientes para simplificar su implementación hasta el punto en que sea simplemente trivial.

Joel Barsotti
fuente
Esto no pareció traer la ventana existente al primer plano cuando lo probé.
RandomEngy
6

El siguiente código es mi solución de tuberías con nombre WCF para registrar una aplicación de instancia única. Es bueno porque también genera un evento cuando otra instancia intenta iniciarse y recibe la línea de comando de la otra instancia.

Está orientado a WPF porque usa la System.Windows.StartupEventHandlerclase, pero esto podría modificarse fácilmente.

Este código requiere una referencia a PresentationFramework, y 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.
    }
}

Código fuente:

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

Nunca debe usar un mutex con nombre para implementar una aplicación de instancia única (o al menos no para el código de producción). El código malicioso puede fácilmente hacer DoS ( denegación de servicio ) su culo ...

Matt Davison
fuente
8
"Nunca debes usar un mutex con nombre" - nunca digas nunca. Si se está ejecutando un código malicioso en mi máquina, probablemente ya esté conectado.
Joe
En realidad, ni siquiera tiene que ser un código malicioso. Podría ser una colisión accidental de nombres.
Matt Davison
Entonces ¿qué deberías hacer?
Kevin Berridge el
La mejor pregunta es qué posible razón desearía ese comportamiento. No diseñe su aplicación como una aplicación de instancia única =). Sé que es una respuesta poco convincente, pero desde el punto de vista del diseño, casi siempre es la respuesta correcta. Sin saber más sobre la aplicación, es difícil decir mucho más.
Matt Davison el
2
Al menos en Windows, los Mutexes tienen control de acceso, por lo que uno puede jugar con su objeto. En cuanto a las colisiones en sí mismas, es por eso que se inventaron UUID / GUID.
NuSkooler
5

Mira el siguiente código. Es una solución excelente y simple para evitar múltiples instancias de una aplicación WPF.

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

    var wLogin = new LoginWindow();

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

Aquí está lo que uso. Combinó la enumeración de procesos para realizar el cambio y mutex para protegerlos de los "clickers activos":

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

Encontré la solución más simple, similar a la de Dale Ragan, pero ligeramente modificada. Hace prácticamente todo lo que necesita y se basa en la clase estándar Microsoft WindowsFormsApplicationBase.

En primer lugar, crea la clase SingleInstanceController, que puede usar en todas las demás aplicaciones de instancia única, que usan Windows Forms:

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


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

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

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

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

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

Luego puede usarlo en su programa de la siguiente manera:

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

Tanto el programa como la solución SingleInstanceController_NET ​​deben hacer referencia a Microsoft.VisualBasic. Si solo desea reactivar la aplicación en ejecución como una ventana normal cuando el usuario intenta reiniciar el programa en ejecución, el segundo parámetro en SingleInstanceController puede ser nulo. En el ejemplo dado, la ventana está maximizada.

Mikhail Semenov
fuente
4

Actualización 2017-01-25. Después de intentar algunas cosas, decidí usar VisualBasic.dll, es más fácil y funciona mejor (al menos para mí). Dejé mi respuesta anterior solo como referencia ...

Solo como referencia, así es como lo hice sin pasar argumentos (lo cual no puedo encontrar ninguna razón para hacerlo ... me refiero a una sola aplicación con argumentos que se pasan de una instancia a otra). Si se requiere la asociación de archivos, se debe instanciar una aplicación (según la expectativa estándar de los usuarios) para cada documento. Si tiene que pasar argumentos a la aplicación existente, creo que usaría vb dll.

Sin pasar args (solo aplicación de instancia única), prefiero no registrar un nuevo mensaje de Windows y no anular el bucle de mensajes como se define en Matt Davis Solution. Aunque no es un gran problema agregar un dll de VisualBasic, prefiero no agregar una nueva referencia solo para hacer una aplicación de instancia única. Además, prefiero instanciar una nueva clase con Main en lugar de llamar a Shutdown from App. Startupup override para asegurarme de salir lo antes posible.

Con la esperanza de que a alguien le guste ... o inspire un poco :-)

La clase de inicio del proyecto debe establecerse como 'SingleInstanceApp'.

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

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

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

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

WindowHelper:

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

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

Sin embargo, sin usar Mutex, respuesta simple:

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

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

Ponlo dentro del Program.Main().
Ejemplo :

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

Puede agregar MessageBox.Showa la ifdeclaración y poner "La aplicación ya se está ejecutando".
Esto podría ser útil para alguien.

newbieguy
fuente
44
Si dos procesos comienzan al mismo tiempo, ambos pueden ver dos procesos activos y terminar automáticamente.
AL
@AT Sí, claro, esto también puede ser útil para las aplicaciones que se ejecutan como Administrador o de lo contrario
newbieguy
Si hace una copia de su aplicación y le cambia el nombre, puede ejecutar el original y la copia al mismo tiempo.
Dominique Bijnens
2

Los enfoques basados ​​en mutex con nombre no son multiplataforma porque los mutex con nombre no son globales en Mono. Los enfoques basados ​​en la enumeración de procesos no tienen ninguna sincronización y pueden dar lugar a un comportamiento incorrecto (por ejemplo, varios procesos iniciados al mismo tiempo pueden terminar automáticamente dependiendo del tiempo). Los enfoques basados ​​en el sistema de ventanas no son deseables en una aplicación de consola. Esta solución, construida sobre la respuesta de Divin, aborda todos estos problemas:

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

Uso Mutex en mi solución para prevenir múltiples instancias.

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

Utilice la solución mutex:

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

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

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

Aquí hay una solución liviana que uso que permite que la aplicación traiga una ventana ya existente al primer plano sin recurrir a mensajes personalizados de Windows o buscar ciegamente los nombres de los procesos.

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

Editar: También puede almacenar e inicializar mutex y createdNew estáticamente, pero deberá deshacerse / liberar explícitamente el mutex una vez que haya terminado con él. Personalmente, prefiero mantener el mutex local, ya que se eliminará automáticamente incluso si la aplicación se cierra sin llegar al final de Main.

Jason Lim
fuente
1

También puede usar CodeFluent Runtime, que es un conjunto gratuito de herramientas. Proporciona una clase SingleInstance para implementar una aplicación de instancia única.

Antoine Diekmann
fuente
1

Agregué un método sendMessage a la clase NativeMethods.

Aparentemente, el método posmensaje no funciona, si la aplicación no se muestra en la barra de tareas, sin embargo, el uso del método sendmessage lo resuelve.

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

Aquí está lo mismo implementado a través del evento.

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

[He proporcionado un código de muestra para las aplicaciones de consola y wpf a continuación.]

Solo tiene que verificar el valor de createdNew variable (¡ejemplo a continuación!), Después de crear la instancia de Mutex con nombre.

El booleano createdNewdevolverá falso:

si la instancia de Mutex llamada "YourApplicationNameHere" ya se creó en algún lugar del sistema

El booleano createdNewdevolverá verdadero:

si este es el primer Mutex llamado "YourApplicationNameHere" en el sistema.


Aplicación de consola - Ejemplo:

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

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);
}
}
Leyendas
fuente
1

Una solución que ahorra tiempo para 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
fuente
1

Por favor verifique la solución propuesta desde aquí que utiliza un semáforo para determinar si una instancia existente ya se está ejecutando, funciona para una aplicación WPF y puede pasar argumentos de la segunda instancia a la primera instancia que ya se está ejecutando utilizando un TcpListener y un TcpClient:

Funciona también para .NET Core, no solo para .NET Framework.

Alexandru Dicu
fuente
1

No puedo encontrar una solución corta aquí, así que espero que a alguien le guste esto:

ACTUALIZADO 2018-09-20

Pon este código en tu 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
fuente
Esto introducirá una condición de carrera. Debe usar un mutex.
georgiosd
1
no hay garantía de que si gira dos instancias al mismo tiempo, esto funcionará. Como actualizar una variable desde dos hilos diferentes. Negocio arriesgado y complicado. Usa la fuerza, Luke :)
georgiosd
@georgiosd ah, entiendo lo que quieres decir. Como si alguien inicia el .exe y cambia el nombre. Sí, esta sería una forma de iniciarlo más veces, pero normalmente el .exe no funciona si se cambia el nombre. Actualizaré mi respuesta ^^ Gracias Luke: D por señalar eso :)
Deniz
1
No solo eso @Deniz. Si inicia dos procesos realmente rápido, existe la posibilidad de que la lista de procesos o el método para obtenerlos se ejecuten mientras todavía solo aparezca uno. Este puede ser un caso marginal irrelevante para usted, pero esta es una pregunta general ...
georgiosd
@georgiosd ¿Puedes probar eso? Porque Ivé lo probó solo para ti, jeje. ¡Pero no fue posible para mí, incluso realmente "realmente rápido"! : P Así que no puedo entender por qué crees en algo que simplemente no es el caso e incluso no te gusta este código inocente: D
Deniz