Qual é a maneira correta de criar um aplicativo WPF de instância única?

657

Usando C # e WPF no .NET (em vez de Windows Forms ou console), qual é a maneira correta de criar um aplicativo que só pode ser executado como uma única instância?

Eu sei que tem algo a ver com alguma coisa mítica chamada mutex, raramente encontro alguém que se incomode em parar e explicar o que é um deles.

O código também precisa informar à instância já em execução que o usuário tentou iniciar uma segunda e talvez também transmitir quaisquer argumentos da linha de comando, se houver algum.

Nidonocu
fonte
14
O CLR não libera automaticamente mutexes não lançados quando o aplicativo é finalizado?
Cocowalla 31/10/2009
1
@Cocowalla: o finalizador deve descartar os mutexes não gerenciados, a menos que não saiba se o mutex foi criado pelo aplicativo gerenciado ou anexado a um existente.
Ignacio Soler Garcia
Ter apenas uma instância do seu aplicativo é razoável. Mas passar argumentos para um aplicativo já existente me parece um pouco tolo. Não vejo razão para fazê-lo. Se você associar um aplicativo à extensão do arquivo, deverá abrir quantos aplicativos o usuário desejar abrir documentos. Esse é o comportamento padrão que todos os usuários esperariam.
Eric Ouellet
9
@Cocowalla O CLR não gerencia recursos nativos. No entanto, se um processo terminar, todos os identificadores serão liberados pelo sistema (o sistema operacional, não o CLR).
11nspectable
1
Prefiro a resposta de @huseyint. Ele usa a classe 'SingleInstance.cs' da Microsoft, para que você não precise se preocupar com Mutexes e IntPtrs. Além disso, não há dependência do VisualBasic (yuk). Veja codereview.stackexchange.com/questions/20871/… para obter mais informações ...
Heliac

Respostas:

537

Aqui está um artigo muito bom sobre a solução Mutex. A abordagem descrita no artigo é vantajosa por dois motivos.

Primeiro, ele não requer uma dependência do assembly Microsoft.VisualBasic. Se meu projeto já dependesse dessa assembléia, eu provavelmente recomendaria o uso da abordagem mostrada em outra resposta . Mas, como é, não uso o assembly Microsoft.VisualBasic e prefiro não adicionar uma dependência desnecessária ao meu projeto.

Segundo, o artigo mostra como trazer a instância existente do aplicativo para o primeiro plano quando o usuário tenta iniciar outra instância. Esse é um toque muito agradável que as outras soluções Mutex descritas aqui não abordam.


ATUALIZAR

Em 1/8/2014, o artigo ao qual vinculei acima ainda está ativo, mas o blog não é atualizado há algum tempo. Isso me preocupa que, eventualmente, ele desapareça e, com ela, a solução preconizada. Estou reproduzindo o conteúdo do artigo aqui para posteridade. As palavras pertencem exclusivamente ao proprietário do blog na Sanity Free Coding .

Hoje, eu queria refatorar algum código que proibia meu aplicativo de executar várias instâncias.

Anteriormente, eu tinha usado o System.Diagnostics.Process para procurar uma instância do meu myapp.exe na lista de processos. Enquanto isso funciona, gera muita sobrecarga e eu queria algo mais limpo.

Sabendo que eu poderia usar um mutex para isso (mas nunca o fiz antes), decidi reduzir meu código e simplificar minha vida.

Na classe do meu aplicativo principal, criei uma estática chamada Mutex :

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

Ter um mutex nomeado nos permite empilhar a sincronização em vários segmentos e processos, o que é exatamente a mágica que estou procurando.

O Mutex.WaitOne tem uma sobrecarga que especifica uma quantidade de tempo para esperarmos. Como não estamos realmente querendo sincronizar nosso código (mais apenas verifique se ele está em uso no momento), usamos a sobrecarga com dois parâmetros: Mutex.WaitOne (tempo limite de tempo limite, bool exitContext) . Espere um retorna true se for capaz de entrar e false se não foi. Nesse caso, não queremos esperar nada; Se o nosso mutex estiver sendo usado, pule-o e siga em frente, então passamos o TimeSpan.Zero (aguarde 0 milissegundos) e configuramos o exitContext como true para que possamos sair do contexto de sincronização antes de tentarmos travar um bloqueio nele. Usando isso, envolvemos nosso código Application.Run em algo assim:

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

Portanto, se o aplicativo estiver em execução, o WaitOne retornará false e receberemos uma caixa de mensagem.

Em vez de mostrar uma caixa de mensagem, optei por utilizar um pouco do Win32 para notificar minha instância em execução de que alguém esqueceu que já estava em execução (colocando-se no topo de todas as outras janelas). Para isso, usei o PostMessage para transmitir uma mensagem personalizada para todas as janelas (a mensagem personalizada foi registrada no RegisterWindowMessage pelo meu aplicativo em execução, o que significa que apenas meu aplicativo sabe o que é) e, em seguida, minha segunda instância é encerrada. A instância do aplicativo em execução receberia essa notificação e a processaria. Para fazer isso, substitui o WndProc no meu formulário principal e ouvi a minha notificação personalizada. Quando recebi essa notificação, defino a propriedade TopMost do formulário como true para trazê-la para cima.

Aqui está o que eu acabei com:

  • 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
fonte
5
Com base em que esta resposta usa menos código e menos bibliotecas e fornece a funcionalidade de subir para o topo, vou fazer desta a nova resposta aceita. Se alguém souber uma maneira mais correta de levar o formulário ao topo usando APIs, fique à vontade para adicioná-lo.
Nidonocu 09/02/09
11
@BlueRaja, você inicia a primeira instância do aplicativo. Quando você inicia a segunda instância do aplicativo, ele detecta que outra instância já está em execução e se prepara para desligar. Antes de fazer isso, ele envia uma mensagem nativa "SHOWME" para a primeira instância, o que leva a primeira instância ao topo. Eventos no .NET não permitem comunicação entre processos, e é por isso que a mensagem nativa é usada.
Matt Davis
7
Existe uma maneira de passar as linhas de comando da outra instância, talvez?
Gyurisc
22
@ Nam, o Mutexconstrutor simplesmente requer uma string, para que você possa fornecer o nome que desejar, por exemplo, "This Is My Mutex". Como um 'Mutex' é um objeto de sistema que está disponível para outros processos, normalmente você deseja que o nome seja exclusivo para que não colidir com outros nomes de 'Mutex' no mesmo sistema. No artigo, a string de aparência enigmática é um 'Guid'. Você pode gerar isso programaticamente chamando System.Guid.NewGuid(). No caso do artigo, o usuário provavelmente o gerou por meio do Visual Studio, como mostrado aqui: msdn.microsoft.com/en-us/library/ms241442(VS.80).aspx
Matt Davis
6
A abordagem mutex pressupõe que o mesmo usuário está tentando iniciar o aplicativo novamente? Certamente, trazer "a instância existente do aplicativo para o primeiro plano" não faz sentido depois de um 'usuário alternar'
dumbledad
107

Você pode usar a classe Mutex, mas em breve descobrirá que precisará implementar o código para transmitir os argumentos e tal. Bem, eu aprendi um truque ao programar no WinForms quando li o livro de Chris Sell . Esse truque usa a lógica que já está disponível para nós na estrutura. Eu não sei sobre você, mas quando aprendo sobre coisas que posso reutilizar na estrutura, esse é geralmente o caminho que tomo em vez de reinventar a roda. A menos, é claro, que não faça tudo o que eu quero.

Quando entrei no WPF, criei uma maneira de usar o mesmo código, mas em um aplicativo WPF. Esta solução deve atender às suas necessidades com base em sua pergunta.

Primeiro, precisamos criar nossa classe de aplicativo. Nesta classe, substituiremos o evento OnStartup e criaremos um método chamado Activate, que será usado posteriormente.

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

Segundo, precisaremos criar uma classe que possa gerenciar nossas instâncias. Antes de passarmos por isso, vamos realmente reutilizar algum código que está no assembly Microsoft.VisualBasic. Como estou usando o C # neste exemplo, tive que fazer uma referência ao assembly. Se você estiver usando o VB.NET, não precisará fazer nada. A classe que vamos usar é WindowsFormsApplicationBase e herdaremos nosso gerenciador de instâncias e, em seguida, aproveitaremos as propriedades e os eventos para lidar com a instância ú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();
    }
}

Basicamente, estamos usando os bits VB para detectar instâncias únicas e processar adequadamente. O OnStartup será acionado quando a primeira instância for carregada. OnStartupNextInstance é acionado quando o aplicativo é executado novamente. Como você pode ver, eu posso entender o que foi passado na linha de comando através dos argumentos do evento. Eu defino o valor para um campo de instância. Você pode analisar a linha de comando aqui ou transmiti-la ao seu aplicativo através do construtor e da chamada para o método Activate.

Terceiro, é hora de criar nosso EntryPoint. Em vez de atualizar o aplicativo como você faria normalmente, aproveitaremos o nosso SingleInstanceManager.

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

Bem, espero que você seja capaz de seguir tudo, poder usar esta implementação e torná-la sua.

Dale Ragan
fonte
9
Eu ficaria com a solução mutex porque não tem nada a ver com formulários.
911 Steven Sudit
1
Eu usei isso porque tive problemas com outras abordagens, mas tenho quase certeza de que ele usa o sistema de comunicação remota. Meu aplicativo teve dois problemas relacionados - alguns clientes dizem que ele tenta ligar para casa, mesmo que tenha solicitado que não o faça. Quando eles olham com mais cuidado, a conexão é com o host local. Ainda assim, eles não sabem disso inicialmente. Além disso, não posso usar o controle remoto para um propósito diferente (eu acho?), Porque ele já está sendo usado para isso. Quando tentei a abordagem mutex, eu poderia usar o sistema remoto novamente.
Richard Watson
4
Perdoe-me, mas, a menos que eu esteja faltando alguma coisa, você evitou escrever 3 linhas de código e, em vez disso, reutilizou o framework apenas para escrever um código bastante pesado para fazer isso. Então, onde estão as economias?
greenoldman
2
é possível fazê-lo em winforms?
Jack
1
Se você não chamar InitializeComponent () na instância do aplicativo, não poderá resolver recursos ... _application = new SingleInstanceApplication (); _application.InitializeComponent (); _application.Run ();
25413 Nick
84

A partir daqui .

Um uso comum para um Mutex entre processos é garantir que apenas a instância de um programa possa ser executada por vez. Veja como é feito:

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

Um bom recurso do Mutex é que, se o aplicativo terminar sem que o ReleaseMutex seja chamado pela primeira vez, o CLR lançará o Mutex automaticamente.

jason saldo
fonte
5
Eu tenho que dizer, eu gosto muito mais desta resposta do que a resposta simplesmente porque ela não depende do WinForms. Pessoalmente, a maior parte do meu desenvolvimento foi transferida para o WPF e não quero precisar extrair bibliotecas do WinForm para algo assim.
Switters 27/10/08
5
Claro que, para ser uma resposta completa, você tem que descrevem também passar os argumentos para a outra instância :)
Simon Buchan
@ Jason, bom, obrigado! Mas eu prefiro não passar nenhum tempo limite. É muito subjetivo e depende de muitas variáveis. Se você quiser permitir que outro aplicativo para começar, basta soltar o mutex mais rápido .. por exemplo, logo que o usuário confirme perto
Eric Ouellet
@ EricOuellet: Praticamente todos os programas que têm guias fazem isso - Photoshop, Sublime Text, Chrome .... Se você tem um bom motivo para ter um processo "mestre" (digamos que tenha um banco de dados em processo) deseja que ele mostre a interface do usuário como se fosse um novo processo também.
Simon Buchan 06/12
@ Simon, você está certo. Eu apenas me questiono sobre uma coisa muito antiga ... MDI vs SDI (Multi documentinterface vs Single document interface). Quando você fala sobre guias, você se refere ao MDI. Em 1998, um livro da Microsoft sugere eliminar todos os aplicativos MDI. A Microsoft mudou o Word, Excel ... para SDI, que eu acho que é mais simples e melhor. Entendo que o Chrome e outros (agora IE) querem voltar ao MDI. Pessoalmente (com base em nada / sentimentos pessoais), ainda é melhor abrir um novo aplicativo quando o arquivo assoc for selecionado. Mas entendo melhor a pergunta feita agora. Obrigado !
Eric Ouellet
58

Na verdade, o MSDN possui um aplicativo de exemplo para C # e VB para fazer exatamente isso: http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx

A técnica mais comum e confiável para o desenvolvimento de detecção de instância única é usar a infraestrutura de comunicação remota do Microsoft .NET Framework (System.Remoting). O Microsoft .NET Framework (versão 2.0) inclui um tipo, WindowsFormsApplicationBase, que encapsula a funcionalidade de comunicação remota necessária. Para incorporar esse tipo em um aplicativo WPF, um tipo precisa derivar dele e ser usado como um calço entre o método de ponto de entrada estático do aplicativo, Principal, e o tipo de Aplicativo do aplicativo WPF. O calço detecta quando um aplicativo é iniciado pela primeira vez e quando tentativas subseqüentes são tentadas, e gera o controle do tipo de Aplicativo WPF para determinar como processar as ativações.

  • No C #, as pessoas respiram fundo e esquecem o conjunto 'Não quero incluir a DLL VisualBasic'. Por causa disso e do que Scott Hanselman diz e do fato de que essa é praticamente a solução mais limpa para o problema e foi projetada por pessoas que sabem muito mais sobre a estrutura do que você.
  • Do ponto de vista da usabilidade, o fato é que, se o usuário está carregando um aplicativo e ele já está aberto, e você está enviando uma mensagem de erro como essa 'Another instance of the app is running. Bye', ele não será um usuário muito feliz. Você simplesmente DEVE (em um aplicativo GUI) alternar para esse aplicativo e passar os argumentos fornecidos - ou se os parâmetros da linha de comando não tiverem significado, será necessário abrir o aplicativo que pode ter sido minimizado.

A estrutura já tem suporte para isso - é apenas que algum idiota nomeou a DLL Microsoft.VisualBasice ela não foi colocada Microsoft.ApplicationUtilsou algo parecido. Supere isso - ou abra o Reflector.

Dica: Se você usar essa abordagem exatamente como está, e já tiver um App.xaml com recursos, etc., também desejará dar uma olhada nisso .

Simon_Weaver
fonte
Obrigado por incluir o link "dê uma olhada também neste". Era exatamente disso que eu precisava. A propósito, a solução nº 3 no seu link é a melhor.
Eternal21
Também sou um defensor da delegação à estrutura e bibliotecas especialmente projetadas, sempre que possível.
Eniola
23

Este código deve ir para o método principal. Veja aqui mais informações sobre o método principal no 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: Os métodos acima assumem que seu processo / aplicativo possui um nome exclusivo. Porque ele usa o nome do processo para descobrir se há processadores existentes. Portanto, se seu aplicativo tiver um nome muito comum (ou seja: Bloco de notas), a abordagem acima não funcionará.

CharithJ
fonte
1
Além disso, isso não funcionará se houver outro programa em execução no seu computador com o mesmo nome. ProcessNameretorna o nome do arquivo executável menos o exe. Se você criar um aplicativo chamado "Bloco de Notas" e o bloco de notas do Windows estiver em execução, ele será detectado como seu aplicativo em execução.
Jcl
1
Obrigado por esta resposta. Eu encontrei muitas perguntas semelhantes e as respostas sempre foram tão elaboradas e / ou confusas que as achei inúteis. Este (método nº 1) é direto, claro e, acima de tudo, me ajudou a fazer meu código rodar.
ElDoRado1239
20

Bem, eu tenho uma classe descartável para isso que funciona facilmente na maioria dos casos de uso:

Use-o assim:

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

        // Initialize program here.
    }
}

Aqui 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
fonte
1
este foi bem fácil de trabalhar. Ele não fecharia o segundo aplicativo até eu alterar Application.Exit (); para um retorno simples; mas fora isso é ótimo. Embora eu admita que vou examinar a solução anterior mais de perto, pois ela usa uma interface. blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/…
hal9000
15

Um novo que usa material Mutex e IPC e também transmite quaisquer argumentos de linha de comando para a instância em execução é o Aplicativo de Instância Única do WPF .

huseyint
fonte
Eu uso isso com grande sucesso. Se você incorporar NamedPipes com isso, também poderá passar argumentos de linha de comando para o aplicativo original. A classe 'SingleInstance.cs' foi escrita pela Microsoft. Eu adicionei outro link para uma versão mais legível do blog de Arik Poznanski no CodeProject.
Heliac
1
Link agora está quebrado.
Mike Lowery
11

O código C # .NET Single Instance Application que é a referência para a resposta marcada é um ótimo começo.

No entanto, descobri que não lida muito bem com os casos em que a instância que já existe possui uma caixa de diálogo modal aberta, seja ela gerenciada (como outra forma como uma caixa about) ou não gerenciada (como a OpenFileDialog, mesmo ao usar a classe .NET padrão). Com o código original, o formulário principal é ativado, mas o modal permanece inativo, o que parece estranho, e o usuário deve clicar nele para continuar usando o aplicativo.

Portanto, criei uma classe de utilitário SingleInstance para lidar com tudo isso automaticamente para aplicativos WinForms e WPF.

Winforms :

1) modifique a classe Program da seguinte maneira:

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 a classe da janela principal assim:

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 a página do aplicativo como esta (e certifique-se de definir sua ação de compilação como página para poder redefinir o método Main):

public partial class App : Application
{
    public static readonly SingleInstance Singleton = new SingleInstance(typeof(App).FullName);

    [STAThread]
    public static void Main(string[] args)
    {
        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        Singleton.RunFirstInstance(() =>
        {
            SingleInstanceMain(args);
        });
    }

    public static void SingleInstanceMain(string[] args)
    {
        // standard code that was in Main now goes here
        App app = new App();
        app.InitializeComponent();
        app.Run();
    }
}

2) modifique a classe da janela principal assim:

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

E aqui está a classe de utilitário:

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

Aqui está um exemplo que permite que você tenha uma única instância de um aplicativo. Quando qualquer nova instância é carregada, eles passam seus argumentos para a instância principal que está sendo executada.

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
fonte
Este é realmente um bom exemplo do que eu devo fazer. Nathan, todos os argumentos são enviados usando esse método? Tenho mais ou menos 7 no meu aplicativo e acho que esse código funcionará.
Kevp 30/05
1
No meu exemplo, apenas o primeiro argumento é enviado, mas pode ser alterado para que todos eles sejam enviados.
Nathan Moinvaziri
8

Apenas algumas considerações: há casos em que é necessário que apenas uma instância de um aplicativo não seja "coxo", como alguns acreditam. Os aplicativos de banco de dados etc. são uma ordem de magnitude mais difícil se permitirmos que várias instâncias do aplicativo acessem um banco de dados por um único usuário (você sabe, tudo isso atualiza todos os registros abertos em várias instâncias do aplicativo nos usuários) máquina etc.). Primeiro, para a "coisa de colisão de nome, não use um nome legível por humanos - use um GUID ou, melhor ainda, um GUID + o nome legível por humanos. As chances de colisão de nomes desapareceram do radar e o Mutex não se importa. Como alguém apontou, um ataque do DOS seria péssimo, mas se a pessoa mal-intencionada tiver o problema de obter o nome do mutex e incorporá-lo ao aplicativo, você é praticamente um alvo de qualquer maneira e precisará fazer MUITO mais para se proteger do que apenas mexer em um nome mutex. Além disso, se alguém usar a variante de: new Mutex (true, "some GUID plus Name", out AIsFirstInstance), você já terá seu indicador sobre se o Mutex é ou não a primeira instância.

Bruce
fonte
6

Tantas respostas para uma pergunta aparentemente simples. Só para agitar um pouco as coisas, aqui está a minha solução para este problema.

Criar um Mutex pode ser problemático porque o JIT-er apenas o vê usando uma pequena parte do seu código e deseja marcá-lo como pronto para a coleta de lixo. Ele quer ser mais esperto do que você pensa que não usará o Mutex por tanto tempo. Na realidade, você deseja manter esse Mutex enquanto o aplicativo estiver em execução. A melhor maneira de dizer ao coletor de lixo para deixá-lo em paz com o Mutex é dizendo-lhe para mantê-lo vivo pelas diferentes gerações de coleta de garagem. Exemplo:

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

Tirei a ideia desta página: http://www.ai.uga.edu/~mc/SingleInstance.html

Pedro
fonte
3
Não seria mais fácil armazenar uma cópia compartilhada na classe do aplicativo?
Rossisdead 26/05/10
6

Parece que existe uma maneira realmente boa de lidar com isso:

Aplicativo de instância única WPF

Isso fornece uma classe que você pode adicionar que gerencia todo o mutex e cruff de mensagens para simplificar a sua implementação até o ponto em que é simplesmente trivial.

Joel Barsotti
fonte
Isso não pareceu trazer a janela existente para o primeiro plano quando tentei.
usar o seguinte
6

O código a seguir é minha solução de pipes nomeados do WCF para registrar um aplicativo de instância única. É bom porque também gera um evento quando outra instância tenta iniciar e recebe a linha de comando da outra instância.

É voltado para o WPF porque usa a System.Windows.StartupEventHandlerclasse, mas isso pode ser facilmente modificado.

Este código requer uma referência a PresentationFramework, e 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 fonte:

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

Você nunca deve usar um mutex nomeado para implementar um aplicativo de instância única (ou pelo menos não para o código de produção). Código malicioso pode facilmente fazer DoS ( Negação de Serviço ) sua bunda ...

Matt Davison
fonte
8
"Você nunca deve usar um mutex nomeado" - nunca diga nunca. Se um código mal-intencionado estiver sendo executado na minha máquina, provavelmente já estou usando uma mangueira.
187 Joe Joe
Na verdade, nem precisa ser um código malicioso. Poderia ser apenas uma colisão acidental de nomes.
21411 Matt Davison
Então o que você deve fazer?
22411 Kevin Berridge
A melhor pergunta é qual o possível motivo para você querer esse comportamento. Não crie seu aplicativo como um aplicativo de instância única =). Eu sei que é uma resposta fraca, mas do ponto de vista do design, é quase sempre a resposta correta. Sem saber mais sobre o aplicativo, é difícil dizer muito mais.
22411 Matt Davison
2
Pelo menos no Windows, os Mutexes têm controle de acesso, para que você possa brincar com seu objeto. Quanto aos nomes das colisões, é por isso que os UUID / GUIDs foram inventados.
NuSkooler 12/10/10
5

Veja o código a seguir. É uma solução excelente e simples para impedir várias instâncias de um aplicativo 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
fonte
4

Aqui está o que eu uso. Ele combinou a enumeração de processos para executar alternância e mutex para proteger contra "clickers ativos":

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

Encontrei a solução mais simples, semelhante à de Dale Ragan, mas ligeiramente modificada. Ele faz praticamente tudo o que você precisa e com base na classe Microsoft WindowsFormsApplicationBase padrão.

Primeiro, você cria a classe SingleInstanceController, que pode ser usada em todos os outros aplicativos de instância única, que usam o 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);
        }
    }
}

Então você pode usá-lo em seu programa da seguinte maneira:

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

O programa e a solução SingleInstanceController_NET ​​devem fazer referência ao Microsoft.VisualBasic. Se você deseja apenas reativar o aplicativo em execução como uma janela normal quando o usuário tenta reiniciar o programa em execução, o segundo parâmetro no SingleInstanceController pode ser nulo. No exemplo dado, a janela é maximizada.

Mikhail Semenov
fonte
4

Atualização 2017-01-25. Depois de tentar algumas coisas, decidi usar o VisualBasic.dll, que é mais fácil e funciona melhor (pelo menos para mim). Deixei minha resposta anterior apenas como referência ...

Apenas como referência, foi assim que passei sem passar argumentos (o que não consigo encontrar nenhuma razão para fazê-lo ... quero dizer, um único aplicativo com argumentos que devem ser passados ​​de uma instância para outra). Se a associação de arquivos for necessária, um aplicativo (conforme a expectativa padrão dos usuários) será instanciado para cada documento. Se você tiver que passar args para o aplicativo existente, acho que usaria a dll vb.

Não passando argumentos (apenas aplicativo de instância única), prefiro não registrar uma nova mensagem do Windows e não substituir o loop de mensagens, conforme definido na Matt Davis Solution. Embora não seja muito importante adicionar uma dll VisualBasic, prefiro não adicionar uma nova referência apenas para criar um aplicativo de instância única. Além disso, prefiro instanciar uma nova classe com Main em vez de chamar Shutdown a partir da substituição App.Startup para garantir a saída o mais rápido possível.

Na esperança de que alguém goste ... ou inspire um pouco :-)

A classe de inicialização do projeto deve ser definida 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
fonte
3

Não usando o Mutex, porém, resposta simples:

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

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

Coloque dentro do Program.Main().
Exemplo :

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

Você pode adicionar MessageBox.Showà ifdeclaração e colocar "Aplicativo já em execução".
Isso pode ser útil para alguém.

newbieguy
fonte
4
Se dois processos iniciarem ao mesmo tempo, eles poderão ver dois processos ativos e terminar automaticamente.
AT
@AT Sim certo, isso também pode ser útil para aplicativos em execução como administrador ou outra pessoa
newbieguy
Se você fizer uma cópia do seu aplicativo e renomeá-lo, poderá executar o original e a cópia ao mesmo tempo.
Dominique Bijnens 25/01
2

As abordagens baseadas em mutex nomeado não são multiplataforma porque os mutexes nomeados não são globais no Mono. As abordagens baseadas em enumeração de processos não têm sincronização e podem resultar em comportamento incorreto (por exemplo, vários processos iniciados ao mesmo tempo podem terminar automaticamente, dependendo do tempo). As abordagens baseadas no sistema de janelas não são desejáveis ​​em um aplicativo de console. Esta solução, construída sobre a resposta de Divin, aborda todos estes 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.");
            }
        }
    }
}
AT
fonte
2

Eu uso o Mutex na minha solução para evitar várias instâncias.

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

Use solução 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
fonte
1

Aqui está uma solução leve que eu uso, que permite que o aplicativo traga uma janela já existente para o primeiro plano sem recorrer a mensagens personalizadas do Windows ou pesquisar cegamente os nomes dos processos.

[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);

static readonly string guid = "<Application Guid>";

static void Main()
{
    Mutex mutex = null;
    if (!CreateMutex(out mutex))
        return;

    // Application startup code.

    Environment.SetEnvironmentVariable(guid, null, EnvironmentVariableTarget.User);
}

static bool CreateMutex(out Mutex mutex)
{
    bool createdNew = false;
    mutex = new Mutex(false, guid, out createdNew);

    if (createdNew)
    {
        Process process = Process.GetCurrentProcess();
        string value = process.Id.ToString();

        Environment.SetEnvironmentVariable(guid, value, EnvironmentVariableTarget.User);
    }
    else
    {
        string value = Environment.GetEnvironmentVariable(guid, EnvironmentVariableTarget.User);
        Process process = null;
        int processId = -1;

        if (int.TryParse(value, out processId))
            process = Process.GetProcessById(processId);

        if (process == null || !SetForegroundWindow(process.MainWindowHandle))
            MessageBox.Show("Unable to start application. An instance of this application is already running.");
    }

    return createdNew;
}

Edit: Você também pode armazenar e inicializar o mutex e createdNew estaticamente, mas precisará descartá-lo / liberar explicitamente o mutex assim que terminar. Pessoalmente, prefiro manter o mutex local, pois ele será descartado automaticamente, mesmo que o aplicativo seja fechado sem chegar ao final do Main.

Jason Lim
fonte
1

Você também pode usar o CodeFluent Runtime, que é um conjunto gratuito de ferramentas. Ele fornece uma classe SingleInstance para implementar um aplicativo de instância única.

Antoine Diekmann
fonte
1

Adicionei um método sendMessage à classe NativeMethods.

Aparentemente, o método do método postmessage funciona, se o aplicativo não for exibido na barra de tarefas, no entanto, o uso do método sendmessage resolve isso.

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

Aqui está a mesma coisa implementada via Event.

public enum ApplicationSingleInstanceMode
{
    CurrentUserSession,
    AllSessionsOfCurrentUser,
    Pc
}

public class ApplicationSingleInstancePerUser: IDisposable
{
    private readonly EventWaitHandle _event;

    /// <summary>
    /// Shows if the current instance of ghost is the first
    /// </summary>
    public bool FirstInstance { get; private set; }

    /// <summary>
    /// Initializes 
    /// </summary>
    /// <param name="applicationName">The application name</param>
    /// <param name="mode">The single mode</param>
    public ApplicationSingleInstancePerUser(string applicationName, ApplicationSingleInstanceMode mode = ApplicationSingleInstanceMode.CurrentUserSession)
    {
        string name;
        if (mode == ApplicationSingleInstanceMode.CurrentUserSession)
            name = $"Local\\{applicationName}";
        else if (mode == ApplicationSingleInstanceMode.AllSessionsOfCurrentUser)
            name = $"Global\\{applicationName}{Environment.UserDomainName}";
        else
            name = $"Global\\{applicationName}";

        try
        {
            bool created;
            _event = new EventWaitHandle(false, EventResetMode.ManualReset, name, out created);
            FirstInstance = created;
        }
        catch
        {
        }
    }

    public void Dispose()
    {
        _event.Dispose();
    }
}
Siarhei Kuchuk
fonte
1

[Forneci código de exemplo para aplicativos de console e wpf abaixo.]

Você só precisa verificar o valor da createdNewvariável (exemplo abaixo!), Depois de criar a instância Mutex nomeada.

O booleano createdNewretornará false:

se a instância do Mutex denominada "YourApplicationNameHere" já tiver sido criada no sistema em algum lugar

O booleano createdNewretornará true:

se este for o primeiro Mutex chamado "YourApplicationNameHere" no sistema.


Aplicativo de console - Exemplo:

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

Exemplo WPF:

public partial class App : Application
{
static Mutex m = null;

protected override void OnStartup(StartupEventArgs e)
{

    const string mutexName = "YourApplicationNameHere";
    bool createdNew = false;

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

        m = new Mutex(true, mutexName, out createdNew);

        if (!createdNew)
        {
            Current.Shutdown(); // Exit the application
        }

    }
    catch (Exception)
    {
        throw;
    }

    base.OnStartup(e);
}


protected override void OnExit(ExitEventArgs e)
{
    if (m != null)
    {
        m.Dispose();
    }
    base.OnExit(e);
}
}
Legendas
fonte
1

Uma solução que economiza tempo 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
fonte
1

Verifique a solução proposta aqui que usa um semáforo para determinar se uma instância existente já está em execução, funciona para um aplicativo WPF e pode passar argumentos da segunda instância para a primeira instância já em execução usando um TcpListener e um TcpClient:

Também funciona para o .NET Core, não apenas para o .NET Framework.

Alexandru Dicu
fonte
1

Não consigo encontrar uma solução curta aqui, então espero que alguém goste disso:

ATUALIZADO 20/09/2018

Coloque este código no seu 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
fonte
Isto irá introduzir uma condição de corrida. Deve usar um mutex.
georgiosd
1
não há garantia de que, se você criar duas instâncias ao mesmo tempo, isso funcionará. Como atualizar uma variável de dois threads diferentes. Negócio arriscado e complicado. Use a força, Luke :) #
1919 georgiosd
@georgiosd ah eu entendo o que você quer dizer. Como se alguém iniciasse o .exe e alterasse o nome. Sim, essa seria uma maneira de iniciá-lo mais vezes, mas normalmente o .exe não funciona, se o nome foi alterado. Vou atualizar a minha resposta ^^ Obrigado Lucas: D por apontar isso :)
Deniz
1
Não é só isso @Deniz. Se você iniciar dois processos muito rapidamente, é possível que a lista de processos ou o método que os busca seja executado enquanto ainda houver apenas um aparecendo. Este pode ser um caso extremo que é irrelevante para você, mas sendo esta uma questão geral ...
georgiosd
@georgiosd Você pode provar isso? Porque Iv'e testou apenas para você hehe. Mas não foi possível para mim, mesmo realmente "muito rápido"! : P Então, eu não consigo entender por que você acredita em algo que simplesmente não é o caso e até não gosta desse código inocente: D
Deniz