Как правильно создать приложение WPF с одним экземпляром?

562

Использование С# и WPF в .NET(вместо Windows Forms или консоль), что является правильным способом создания приложения, которое может запускаться как один экземпляр?

Я знаю, что это имеет какое-то отношение к какой-то мифической вещи, называемой мьютексом, редко можно найти кого-то, кто останавливается и объясняет, что это такое.

Код должен также информировать уже запущенный экземпляр, который пользователь попытался запустить второй, и, возможно, также передать любые аргументы командной строки, если они существуют.

  • 11
    Разве CLR автоматически не освобождает любые невыпущенные мьютексы, когда приложение все равно завершается?
  • 1
    @Cocowalla: финализатор должен располагать неуправляемыми мьютексами, если он не может знать, был ли мьютекс создан управляемым приложением или присоединен к существующему.
Показать ещё 5 комментариев
Теги:
wpf
mutex

35 ответов

453
Лучший ответ

Вот очень хорошая статья относительно решения Mutex. Подход, описанный в статье, выгоден по двум причинам.

Во-первых, это не требует зависимости от сборки Microsoft.VisualBasic. Если бы у моего проекта уже была зависимость от этой сборки, я бы, вероятно, выступал за использование подхода, показанного в принятом ответе. Но так как это, я не использую сборку Microsoft.VisualBasic, и я бы предпочел не добавлять ненужную зависимость к моему проекту.

Во-вторых, в статье показано, как перенести существующий экземпляр приложения на передний план, когда пользователь пытается запустить другой экземпляр. Это очень приятно, что другие решения Mutex, описанные здесь, не адресуются.


UPDATE

По состоянию на 8/1/2014, статья I, связанная с выше, по-прежнему активна, но блог не обновлялся через некоторое время. Это заставляет меня беспокоиться о том, что в конечном итоге он может исчезнуть, а вместе с ним и защищенное решение. Я воспроизвожу содержание статьи здесь для потомков. Слова принадлежат исключительно владельцу блога в Sanity Free Coding.

Сегодня мне захотелось реорганизовать какой-то код, запрещающий мое приложение от запуска нескольких экземпляров самого себя.

Раньше я использовал System.Diagnostics.Process для поиска экземпляр my my.exe в списке процессов. Хотя это работает, это приносит много накладных расходов, и я хотел что-то более чистое.

Зная, что я могу использовать мьютекс для этого (но никогда не делал этого раньше) Я решил сократить свой код и упростить свою жизнь.

В классе моего основного приложения я создал статическую с именем Mutex:

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

Наличие именованного мьютекса позволяет нам синхронизировать синхронизацию несколько потоков и процессов, которые являются только волшебством, которое я ищу для.

Mutex.WaitOne имеет перегрузку, которая определяет количество времени для нас ждать. Поскольку мы фактически не хотим синхронизировать наш код (более просто проверьте, используется ли он в настоящее время), мы используем перегрузку с два параметра: Mutex.WaitOne(Тайм-аут тайм-аута, bool exitContext). Подождите, пока он вернется, если он в состоянии ввести, и false, если это не так. В этом случае мы не хотим ждать вообще; Если наш мьютекс использовать, пропустить его и двигаться дальше, поэтому мы переходим в TimeSpan.Zero(wait 0 миллисекунды) и установите для параметра exitContext значение true, чтобы мы могли выйти из синхронизации, прежде чем мы попытаемся использовать блокировку. С помощью это, мы завершаем наш Application.Run код внутри что-то вроде этого:

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

Итак, если наше приложение запущено, WaitOne вернет false, и мы получим окно сообщения.

Вместо того, чтобы показывать окно сообщения, я решил использовать немного Win32 для уведомить мой рабочий экземпляр о том, что кто-то забыл, что он уже (приведя себя к вершине всех остальных окон). к для этого я использовал PostMessage для трансляции пользовательского сообщения для каждого (пользовательское сообщение было зарегистрировано с помощью RegisterWindowMessageпо моему запущенному приложению, что означает, что только мое приложение знает, что это), тогда мой второй экземпляр выходит. Исходный экземпляр приложения получит это уведомление и обработает его. Чтобы сделать это, я overrode WndProc в моей основной форме и выслушал мои настройки уведомление. Когда я получил это уведомление, я установил форму Свойство TopMost - true, чтобы поднять его вверх.

Вот что я закончил:

  • 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(частичная сторона фронта)
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;
    }
}
  • 3
    Исходя из того, что в этом ответе используется меньше кода и меньше библиотек, а также обеспечивается функциональность поднятия наверх, я собираюсь сделать этот ответ новым принятым. Если кто-нибудь знает более правильный способ довести форму до вершины, используя API, не стесняйтесь добавлять это.
  • 1
    Это хороший маленький пример - прекрасно работает в моем приложении. Добавить это просто реализовать.
Показать ещё 34 комментария
98

Вы можете использовать класс Mutex, но вскоре вы узнаете, что вам нужно будет реализовать код для передачи аргументов и таких себя. Ну, я узнал трюк при программировании в WinForms, когда читаю книгу Chris Sell. Этот трюк использует логику, которая уже доступна нам в рамках. Я не знаю о вас, но когда я узнаю о вещах, которые я могу использовать в рамках, это обычно маршрут, который я беру вместо того, чтобы изобретать колесо. Если, конечно, это не делает все, что я хочу.

Когда я попал в WPF, я придумал способ использовать тот же код, но в приложении WPF. Это решение должно отвечать вашим потребностям, исходя из вашего вопроса.

Во-первых, нам нужно создать наш класс приложения. В этом классе мы переопределяем событие OnStartup и создаем метод под названием Activate, который будет использоваться позже.

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

Во-вторых, нам нужно создать класс, который может управлять нашими экземплярами. Прежде чем мы это рассмотрим, мы намереваемся повторно использовать код, который находится в сборке Microsoft.VisualBasic. Поскольку в этом примере я использую С#, мне нужно было сделать ссылку на сборку. Если вы используете VB.NET, вам не нужно ничего делать. Класс, который мы собираемся использовать, - это WindowsFormsApplicationBase и наследует наш диспетчер экземпляров, а затем использует свойства и события для обработки единого экземпляра.

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

В принципе, мы используем биты VB для обнаружения отдельного экземпляра и процесса соответственно. OnStartup будет запущен, когда загрузится первый экземпляр. OnStartupNextInstance запускается при повторном запуске приложения. Как вы можете видеть, я могу перейти к тому, что было передано в командной строке через аргументы события. Я устанавливаю значение в поле экземпляра. Вы можете проанализировать командную строку здесь или передать ее в приложение через конструктор и вызов метода Activate.

В-третьих, пришло время создать наш EntryPoint. Вместо того, чтобы запускать приложение, как обычно, мы будем использовать наш SingleInstanceManager.

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

Хорошо, я надеюсь, что вы сможете следить за всем и быть в состоянии использовать эту реализацию и сделать ее своей.

  • 0
    Это то, как мы это делаем, и я никогда не был так рад этому из-за зависимости от WinForms.
  • 5
    Я бы придерживался решения мьютекса, потому что оно не имеет ничего общего с формами.
Показать ещё 7 комментариев
79

Из здесь.

Общепринятое использование для Mutex кросс-процесса - гарантировать, что только экземпляр программы может запускаться одновременно. Вот как это делается:

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

Хорошей особенностью Mutex является то, что если приложение завершается без вызова ReleaseMutex, CLR автоматически отключит Mutex.

  • 5
    Должен сказать, мне этот ответ нравится гораздо больше, чем принятый, просто потому, что он не зависит от WinForms. Лично большая часть моей разработки перешла на WPF, и я не хочу тянуть в библиотеки WinForm что-то вроде этого.
  • 4
    Конечно, чтобы получить полный ответ, вы также должны описать передачу аргументов другому экземпляру :)
Показать ещё 5 комментариев
47

MSDN фактически имеет пример приложения для С# и VB, чтобы выполнить именно это: http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx

Наиболее распространенным и надежным методом для разработки обнаружения одного экземпляра является использование инфраструктуры удаленного доступа Microsoft.NET Framework (System.Remoting). Microsoft.NET Framework (версия 2.0) включает в себя тип WindowsFormsApplicationBase, который инкапсулирует требуемую функциональность удаленного взаимодействия. Чтобы включить этот тип в приложение WPF, тип должен быть получен из него и использоваться как прокладка между методом статической точки входа приложения, основным и приложением приложения приложения WPF. Прокладка определяет, когда приложение запускается впервые, и когда предпринимаются последующие попытки запуска, и дает возможность управлять типом приложения WPF, чтобы определить, как обрабатывать запуски.

  • Для людей С# просто сделайте глубокий вдох и забудьте о целом: "Я не хочу включать VisualBasic DLL". Из-за этого и того, что говорит Скотт Гензельман, и того факта, что это в значительной степени является самым чистым решением проблемы и разработано людьми, которые знают намного больше о структуре, чем вы.
  • С точки зрения удобства использования факт заключается в том, что пользователь загружает приложение и он уже открыт, и вы даете им сообщение об ошибке, например 'Another instance of the app is running. Bye' 'Another instance of the app is running. Bye' тогда они не будут очень счастливым пользователем. Вы просто ДОЛЖНЫ (в приложении GUI) переключиться на это приложение и передать предоставленные аргументы - или если параметры командной строки не имеют смысла, вы должны открыть приложение, которое может быть сведено к минимуму.

Рамка уже имеет поддержку для этого - ее просто то, что какой-то идиот назвал DLL Microsoft.VisualBasic и он не попал в Microsoft.ApplicationUtils или что-то в этом роде. Отойдите над ним - или откройте рефлектор.

Совет. Если вы используете этот подход точно так, как есть, и у вас уже есть App.xaml с ресурсами и т.д., Вы тоже захотите взглянуть на это.

  • 0
    Спасибо за то, что вы включили ссылку "взгляните на это тоже". Это именно то, что мне было нужно. Кстати, решение № 3 по вашей ссылке - лучшее.
  • 0
    Я также сторонник делегирования в рамках и специально разработанные библиотеки, где это возможно.
21

Этот код должен перейти к основному методу. Посмотрите here для получения дополнительной информации об основном методе в 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; 
    }
}

Метод 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(...);
    }
}

Примечание.. Вышеуказанные методы предполагают, что ваш процесс/приложение имеет уникальное имя. Потому что он использует имя процесса, чтобы найти какие-либо существующие процессоры. Итак, если ваше приложение имеет очень распространенное имя (например, "Блокнот" ), вышеуказанный подход не будет работать.

  • 1
    Способ 2 не будет работать, если запускать от имени администратора
  • 1
    Кроме того, это не будет работать, если на вашем компьютере запущена другая программа с таким же именем. ProcessName возвращает имя исполняемого файла минус exe . Если вы создадите приложение под названием «Блокнот», и блокнот Windows будет запущен, он обнаружит его как запущенное приложение.
Показать ещё 1 комментарий
15

Ну, у меня есть одноразовый класс для этого, который легко работает для большинства случаев:

Используйте его следующим образом:

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

        // Initialize program here.
    }
}

Вот он:

/// <summary>
/// Represents a <see cref="SingleInstanceMutex"/> class.
/// </summary>
public partial class SingleInstanceMutex : IDisposable
{
    #region Fields

    /// <summary>
    /// Indicator whether another instance of this application is running or not.
    /// </summary>
    private bool isNoOtherInstanceRunning;

    /// <summary>
    /// The <see cref="Mutex"/> used to ask for other instances of this application.
    /// </summary>
    private Mutex singleInstanceMutex = null;

    /// <summary>
    /// An indicator whether this object is beeing actively disposed or not.
    /// </summary>
    private bool disposed;

    #endregion

    #region Constructor

    /// <summary>
    /// Initializes a new instance of the <see cref="SingleInstanceMutex"/> class.
    /// </summary>
    public SingleInstanceMutex()
    {
        this.singleInstanceMutex = new Mutex(true, Assembly.GetCallingAssembly().FullName, out this.isNoOtherInstanceRunning);
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets an indicator whether another instance of the application is running or not.
    /// </summary>
    public bool IsOtherInstanceRunning
    {
        get
        {
            return !this.isNoOtherInstanceRunning;
        }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Closes the <see cref="SingleInstanceMutex"/>.
    /// </summary>
    public void Close()
    {
        this.ThrowIfDisposed();
        this.singleInstanceMutex.Close();
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            /* Release unmanaged ressources */

            if (disposing)
            {
                /* Release managed ressources */
                this.Close();
            }

            this.disposed = true;
        }
    }

    /// <summary>
    /// Throws an exception if something is tried to be done with an already disposed object.
    /// </summary>
    /// <remarks>
    /// All public methods of the class must first call this.
    /// </remarks>
    public void ThrowIfDisposed()
    {
        if (this.disposed)
        {
            throw new ObjectDisposedException(this.GetType().Name);
        }
    }

    #endregion
}
  • 1
    с этим было довольно легко начать работать. Это не будет закрывать второе приложение, пока я не изменил Application.Exit (); на простое возвращение; но кроме этого это здорово. Хотя я признаю, что я собираюсь взглянуть на предыдущее решение ближе, так как оно использует интерфейс. blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/...
12

Новый, который использует материал Mutex и IPC, а также передает любые аргументы командной строки запущенному экземпляру, однофазное приложение WPF.

  • 0
    Я использую это с большим успехом. Если вы включите NamedPipes с этим, вы также можете передать аргументы командной строки в исходное приложение. Класс SingleInstance.cs был написан Microsoft. Я добавил еще одну ссылку на более читаемую версию блога Арика Познанского на CodeProject.
9

Приложение С#.NET Single Instance, которое является ссылкой для отмеченного ответа, является отличным началом.

Тем не менее, я обнаружил, что он не очень хорошо справляется с случаями, когда уже существующий экземпляр имеет модальный диалог, независимо от того, является ли этот диалог управляемым (например, другой формой, например, о поле), или неуправляемым (например, OpenFileDialog даже при использовании стандартного класса.NET). С исходным кодом активна основная форма, но модальная остается неактивной, что выглядит странно, плюс пользователь должен нажать на нее, чтобы продолжать использовать приложение.

Итак, я создал класс утилиты SingleInstance, чтобы обрабатывать все это довольно автоматически для Winforms и приложений WPF.

Winforms:

1) измените класс программы следующим образом:

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) измените класс главного окна следующим образом:

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) измените страницу приложения таким образом (и убедитесь, что вы установили его действие сборки на страницу, чтобы переопределить метод 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) измените класс главного окна следующим образом:

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

И вот полезный класс:

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

Вот пример, который позволяет вам иметь один экземпляр приложения. Когда все новые экземпляры загружаются, они передают свои аргументы основному экземпляру, который запущен.

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 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);
}
  • 0
    Это действительно хороший пример того, что мне делать. Натан, все ли аргументы отправлены с использованием этого метода? У меня есть 7 или около того в моем приложении, и я думаю, что этот код будет работать.
  • 1
    В моем примере отправляется только первый аргумент, но его можно изменить, чтобы все они были отправлены.
8

Просто мысли: Бывают случаи, когда требуется, чтобы только один экземпляр приложения не был "хромым", как некоторые могли бы поверить. Приложения баз данных и т.д. На порядок сложнее, если разрешить нескольким экземплярам приложения доступ к базе данных одному пользователю (знаете, все, что обновляет все записи, открытые в нескольких экземплярах приложения для пользователей машина и т.д.). Во-первых, для "столкновения с именем" не используйте человекочитаемое имя - вместо этого используйте GUID или, даже лучше GUID + человекочитаемое имя. Шансы столкновения имен просто выпадали из радара, а Mutex не волнует Как заметил кто-то, атака DOS будет сосать, но если злонамеренный человек столкнулся с проблемой получения имени мьютекса и включения его в свое приложение, вы все равно в значительной степени являетесь целью и должны будете делать БОЛЬШЕ еще больше, чтобы защитить а не просто возиться с мьютексом. Кроме того, если вы используете вариант: новый Mutex (true, "GUID плюс имя", вне AIsFirstInstance), у вас уже есть индикатор того, является ли Mutex первым экземпляром.

6

Следующий код - это мое решение WCF с именем pipe для регистрации приложения с одним экземпляром. Это хорошо, потому что он также вызывает событие, когда другой экземпляр пытается запустить, и получает командную строку другого экземпляра.

Он ориентирован на WPF, потому что он использует класс System.Windows.StartupEventHandler, но это может быть легко изменено.

Этот код требует ссылки на PresentationFramework и System.ServiceModel.

Применение:

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

Исходный код:

/// <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 startup information.
  /// </summary>
  /// <param name="guid">The application 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 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 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 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 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);
      }
    }
  }
}
6

Похоже, есть действительно хороший способ справиться с этим:

Приложение с одним экземпляром WPF

Это обеспечивает класс, который вы можете добавить, который управляет всеми мьютексами и сообщениями, чтобы упростить вашу реализацию до такой степени, что это просто тривиально.

  • 0
    Это не вывело существующее окно на передний план, когда я попробовал это.
6

Так много ответов на такой, казалось бы, простой вопрос. Просто немного пошатнуть, вот мое решение этой проблемы.

Создание Mutex может быть проблематичным, потому что JIT-er видит, что вы используете его для небольшой части вашего кода и хотите пометить его как готовый к сбору мусора. Это в значительной степени хочет вывести вас из ума, думая, что вы не собираетесь использовать этот Mutex так долго. На самом деле вы хотите висеть на этом Mutex до тех пор, пока ваше приложение работает. Лучший способ рассказать сборщику мусора о том, чтобы оставить вас только Mutex, - это сказать, чтобы он оставался в живых, хотя из разных поколений коллекции гаража. Пример:

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

Я снял эту идею с этой страницы: http://www.ai.uga.edu/~mc/SingleInstance.html

  • 2
    Разве не было бы проще хранить его общую копию в классе приложения?
5

Вы не должны использовать именованный мьютекс для реализации приложения с одним экземпляром (или, по крайней мере, не для производственного кода). Вредоносный код может легко DoS (Отказ в обслуживании) Ваша задница...

  • 6
    «Никогда не используйте именованный мьютекс» - никогда не говори никогда. Если на моей машине запущен вредоносный код, возможно, я уже скрылся.
  • 0
    На самом деле это даже не должен быть вредоносный код. Это может быть просто случайное столкновение имен.
Показать ещё 3 комментария
4

Посмотрите на следующий код. Это отличное и простое решение для предотвращения нескольких экземпляров приложения WPF.

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

    var wLogin = new LoginWindow();

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

Я нашел более простое решение, похожее на Dale Ragan's, но слегка измененное. Он выполняет практически все, что вам нужно, и основан на стандартном классе Microsoft WindowsFormsApplicationBase.

Во-первых, вы создаете класс SingleInstanceController, который вы можете использовать во всех других приложениях с одним экземпляром, которые используют 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);
        }
    }
}

Затем вы можете использовать его в своей программе следующим образом:

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

Как программа, так и решение SingleInstanceController_NET ​​должны ссылаться на Microsoft.VisualBasic. Если вы просто хотите повторно активировать запущенное приложение как обычное окно, когда пользователь пытается перезапустить запущенную программу, второй параметр в SingleInstanceController может быть нулевым. В данном примере окно максимизируется.

4

Вот что я использую. Он объединяет перечисление процессов для переключения и мьютекса для защиты от "активных кликеров":

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

Не использовать Mutex, хотя, простой ответ:

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

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

Поместите его в Program.Main().
Пример:

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

Вы можете добавить MessageBox.Show в if -statement и поставить "Приложение уже запущено".
Это может быть полезно кому-то.

  • 2
    Если два процесса запускаются одновременно, они могут одновременно видеть два активных процесса и завершать себя.
  • 0
    @AT Да, верно, это также может быть полезно для приложений, работающих от имени администратора или других
2

Обновление 2017-01-25. После нескольких попыток, я решил пойти с VisualBasic.dll, это проще и работает лучше (по крайней мере для меня). Я дал свой предыдущий ответ так же, как ссылку...

Так же, как ссылка, я так и сделал, не передавая аргументы (что я не могу найти повода для этого... Я имею в виду одно приложение с аргументами, которые должны быть переданы из одного экземпляра в другой), Если требуется ассоциация файлов, то для каждого документа должно быть установлено приложение (для каждого стандартного ожидания пользователей). Если вам нужно передать args в существующее приложение, я думаю, что я использовал vb dll.

Не передавая args (только одно экземплярное приложение), я предпочитаю не регистрировать новое сообщение Window и не переопределять петлю сообщений, как определено в Matt Davis Solution. Хотя это не очень важно для добавления DLL VisualBasic, но я предпочитаю не добавлять новую ссылку только для создания одного экземпляра приложения. Кроме того, я предпочитаю инициализировать новый класс с помощью Main вместо того, чтобы вызывать Shutdown из App.Startup override, чтобы обеспечить как можно скорее выход.

В надежде, что кому-нибудь понравится... или немного вдохновит:-)

Класс запуска проекта должен быть установлен как "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);
1

Я не могу найти короткое решение здесь soooo. Надеюсь, кому-то это понравится:

ОБНОВЛЕНО 2018-09-20

(Кстати, введите код в "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;
        }

//Необязательный код. Если вы не хотите, чтобы кто-то работал, вы ".exe" с другим именем:

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

        //Following Code is default code:
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());
    }
  • 0
    Это введет условия гонки. Нужно использовать мьютекс.
  • 1
    нет гарантии, что если вы раскрутите два экземпляра одновременно, это сработает. Как обновление переменной из двух разных потоков. Хитрый рискованный бизнес. Используй силу, Люк :)
Показать ещё 5 комментариев
1

Здесь то же самое реализовано через Event.

public enum ApplicationSingleInstanceMode
{
    CurrentUserSession,
    AllSessionsOfCurrentUser,
    Pc
}

public class ApplicationSingleInstancePerUser: IDisposable
{
    private readonly EventWaitHandle _event;

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

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

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

    public void Dispose()
    {
        _event.Dispose();
    }
}
1

Вы также можете использовать CodeFluent Runtime, который является бесплатным набором инструментов. Он предоставляет класс SingleInstance для реализации одного экземпляра приложения.

1

Здесь используется облегченное решение, которое позволяет приложению выводить уже существующее окно на передний план, не прибегая к пользовательским сообщениям Windows или слепо поиска имен процессов.

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

Редактировать. Вы также можете хранить и инициализировать мьютексы и создаватьNew статически, но вам нужно будет явно удалять/освобождать мьютекс, как только вы закончите с ним. Лично я предпочитаю хранить мьютексы локально, поскольку он будет автоматически удалён, даже если приложение закрывается, не достигнув конца Main.

1

Использовать решение 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;
            }
        }
    }
}
}
0

Решение для экономии времени для С# 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);
        }
    }
}
0

[Я привел пример кода для приложений консоли и wpf ниже.]

Вам нужно только проверить значение переменной createdNew (пример ниже!) после создания именованного экземпляра Mutex.

Логическое createdNew вернет false:

если экземпляр Mutex с именем "YourApplicationNameHere" уже был созданный в системе где-то

Логическое createdNew вернет true:

если это первый мутекс с именем "YourApplicationNameHere" на система.


Консольное приложение - Пример:
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-Пример:

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

Подходы, основанные на именованных мьютексах, не являются кросс-платформенными, поскольку именованные мьютексы не являются глобальными в Mono. Подходы, основанные на процессах, не имеют никакой синхронизации и могут приводить к некорректному поведению (например, несколько процессов, запущенных в одно и то же время, могут полностью завершаться в зависимости от времени). Подходы на основе оконной системы нежелательны в консольном приложении. Это решение, построенное на основе ответа Divin, решает все эти проблемы:

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

Просто используя StreamWriter, как насчет этого?

System.IO.File.StreamWriter OpenFlag = null;   //globally

и

try
{
    OpenFlag = new StreamWriter(Path.GetTempPath() + "OpenedIfRunning");
}
catch (System.IO.IOException) //file in use
{
    Environment.Exit(0);
}
0

Мне нравится решение разрешить несколько экземпляров, если exe вызывается из другого пути. Я модифицировал решение CharithJ Метод 1:

   static class Program {
    [DllImport("user32.dll")]
    private static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
    [DllImport("User32.dll")]
    public static extern Int32 SetForegroundWindow(IntPtr hWnd);
    [STAThread]
    static void Main() {
        Process currentProcess = Process.GetCurrentProcess();
        foreach (var process in Process.GetProcesses()) {
            try {
                if ((process.Id != currentProcess.Id) && 
                    (process.ProcessName == currentProcess.ProcessName) &&
                    (process.MainModule.FileName == currentProcess.MainModule.FileName)) {
                    ShowWindow(process.MainWindowHandle, 5); // const int SW_SHOW = 5; //Activates the window and displays it in its current size and position. 
                    SetForegroundWindow(process.MainWindowHandle);
                    return;
                }
            } catch (Exception ex) {
                //ignore Exception "Access denied "
            }
        }

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}
0

Вот мои 2 цента

 static class Program
    {
        [STAThread]
        static void Main()
        {
            bool createdNew;
            using (new Mutex(true, "MyApp", out createdNew))
            {
                if (createdNew) {
                    Application.EnableVisualStyles();
                    Application.SetCompatibleTextRenderingDefault(false);
                    var mainClass = new SynGesturesLogic();
                    Application.ApplicationExit += mainClass.tray_exit;
                    Application.Run();
                }
                else
                {
                    var current = Process.GetCurrentProcess();
                    foreach (var process in Process.GetProcessesByName(current.ProcessName).Where(process => process.Id != current.Id))
                    {
                        NativeMethods.SetForegroundWindow(process.MainWindowHandle);
                        break;
                    }
                }
            }
        }
    }
  • 0
    Что такое класс "NativeMethods"?
0

Вот как я в конечном итоге заботился об этой проблеме. Обратите внимание, что код отладки все еще присутствует для тестирования. Этот код находится внутри OnStartup в файле App.xaml.cs. (WPF)

        // Process already running ? 
        if (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length > 1)
        {

            // Show your error message
            MessageBox.Show("xxx is already running.  \r\n\r\nIf the original process is hung up you may need to restart your computer, or kill the current xxx process using the task manager.", "xxx is already running!", MessageBoxButton.OK, MessageBoxImage.Exclamation);

            // This process 
            Process currentProcess = Process.GetCurrentProcess();

            // Get all processes running on the local computer.
            Process[] localAll = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName);

            // ID of this process... 
            int temp = currentProcess.Id;
            MessageBox.Show("This Process ID:  " + temp.ToString());

            for (int i = 0; i < localAll.Length; i++)
            {
                // Find the other process 
                if (localAll[i].Id != currentProcess.Id)
                {
                    MessageBox.Show("Original Process ID (Switching to):  " + localAll[i].Id.ToString());

                    // Switch to it... 
                    SetForegroundWindow(localAll[i].MainWindowHandle);

                }
            }

            Application.Current.Shutdown();

        }

У этого могут быть проблемы, которые я еще не поймал. Если я столкнулся с проблемой, я обновлю свой ответ.

0

Вот решение:

Protected Overrides Sub OnStartup(e As StartupEventArgs)
    Const appName As String = "TestApp"
    Dim createdNew As Boolean
    _mutex = New Mutex(True, appName, createdNew)
    If Not createdNew Then
        'app is already running! Exiting the application
        MessageBox.Show("Application is already running.")
        Application.Current.Shutdown()
    End If
    MyBase.OnStartup(e)
End Sub
  • 0
    Мне нравятся простые решения, поэтому я попробовал это сначала ... не смог заставить его работать.
0

Обычно всякий раз, когда мы запускаем .exe, каждый раз, когда он создает отдельный процесс Windows с собственным адресным пространством, ресурсами и т.д. Но мы не хотим этого критерия, поскольку это мешает нам создавать единый процесс. Приложения с одним экземпляром могут быть созданы с использованием Mutex в С#, который обсуждается в этой статье

Более того, если мы хотим, чтобы приложение было сверху, мы можем сделать это, используя

 [DllImport("user32")]
 static extern IntPtr SetForegroundWindow(IntPtr hWnd);
0

Я добавил метод sendMessage в класс NativeMethods.

По-видимому, метод дозирования postmessage работает, если приложение не отображается на панели задач, однако использование метода sendmessage разрешает это.

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

Как правило, это код, который я использую для приложений с одним экземпляром Windows Forms:

[STAThread]
public static void Main()
{
    String assemblyName = Assembly.GetExecutingAssembly().GetName().Name;

    using (Mutex mutex = new Mutex(false, assemblyName))
    {
        if (!mutex.WaitOne(0, false))
        {
            Boolean shownProcess = false;
            Process currentProcess = Process.GetCurrentProcess();

            foreach (Process process in Process.GetProcessesByName(currentProcess.ProcessName))
            {
                if (!process.Id.Equals(currentProcess.Id) && process.MainModule.FileName.Equals(currentProcess.MainModule.FileName) && !process.MainWindowHandle.Equals(IntPtr.Zero))
                {
                    IntPtr windowHandle = process.MainWindowHandle;

                    if (NativeMethods.IsIconic(windowHandle))
                        NativeMethods.ShowWindow(windowHandle, ShowWindowCommand.Restore);

                    NativeMethods.SetForegroundWindow(windowHandle);

                    shownProcess = true;
                }
            }

            if (!shownProcess)
                MessageBox.Show(String.Format(CultureInfo.CurrentCulture, "An instance of {0} is already running!", assemblyName), assemblyName, MessageBoxButtons.OK, MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
        }
        else
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form());
        }
    }
}

Где нативные компоненты:

[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean IsIconic([In] IntPtr windowHandle);

[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean SetForegroundWindow([In] IntPtr windowHandle);

[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean ShowWindow([In] IntPtr windowHandle, [In] ShowWindowCommand command);

public enum ShowWindowCommand : int
{
    Hide                   = 0x0,
    ShowNormal             = 0x1,
    ShowMinimized          = 0x2,
    ShowMaximized          = 0x3,
    ShowNormalNotActive    = 0x4,
    Minimize               = 0x6,
    ShowMinimizedNotActive = 0x7,
    ShowCurrentNotActive   = 0x8,
    Restore                = 0x9,
    ShowDefault            = 0xA,
    ForceMinimize          = 0xB
}
  • 0
    Проблема этой реализации заключается в том, что вы не можете предоставить аргументы командной строки от второго экземпляра до первого. Для лучшего объяснения смотрите здесь .
  • 0
    Не похоже, что вопрос требует этого. В любом случае, это не будет последовательным поведением ... закрываемый экземпляр не должен изменять поведение существующего. Если вы хотите, чтобы ваше приложение работало иначе, вы закрываете текущий процесс и запускаете новый с другими параметрами.
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню