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

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

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

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

21.08.2008 00:33:59
Разве CLR автоматически не освобождает любые невыпущенные мьютексы, когда приложение все равно завершается?
Cocowalla 31.10.2009 14:08:40
@Cocowalla: финализатор должен располагать неуправляемыми мьютексами, если он не может знать, был ли мьютекс создан управляемым приложением или присоединен к существующему.
Ignacio Soler Garcia 30.05.2012 09:41:09
Разумно иметь только один экземпляр вашего приложения. Но передача аргументов уже существующему приложению кажется мне немного глупой. Я не вижу причин для этого. Если вы связываете приложение с расширением файла, вы должны открыть столько приложений, сколько пользователь захочет открыть документы. Это стандартное поведение, которого ожидают все пользователи.
Eric Ouellet 5.12.2013 21:36:30
@Cocowalla CLR не управляет собственными ресурсами. Однако, если процесс завершается, все дескрипторы освобождаются системой (ОС, а не CLR).
IInspectable 1.01.2014 18:53:21
Я предпочитаю ответ @huseyint. Он использует собственный класс Microsoft «SingleInstance.cs», поэтому вам не нужно беспокоиться о мьютексах и IntPtrs. Также нет зависимости от VisualBasic (юк). См. Codereview.stackexchange.com/questions/20871/… Подробнее ...
Heliac 26.02.2014 12:16:28
30 ОТВЕТОВ
РЕШЕНИЕ

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

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

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


ОБНОВИТЬ

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

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

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

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

В классе main моего приложения я создал статический файл с именем Mutex :

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

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

Mutex.WaitOne имеет перегрузку, которая определяет время ожидания. Поскольку мы на самом деле не хотим синхронизировать наш код (больше просто проверьте, используется ли он в данный момент), мы используем перегрузку с двумя параметрами: Mutex.WaitOne (Timespan timeout, bool exitContext) . Подождите, один возвращает истину, если он может войти, и ложь, если это не так. В этом случае мы вообще не хотим ждать; Если наш мьютекс используется, пропустите его и двигайтесь дальше, поэтому мы передаем TimeSpan.Zero (ожидание 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 моим запущенным приложением, что означает, что только мое приложение знает, что это такое), после чего мой второй экземпляр завершается. Работающий экземпляр приложения получит это уведомление и обработает его. Чтобы сделать это, я переопределил 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;
    }
}
530
4.03.2019 19:50:40
Исходя из того, что в этом ответе используется меньше кода и меньше библиотек, а также обеспечивается функциональность поднятия наверх, я собираюсь сделать этот ответ новым принятым. Если кто-нибудь знает более правильный способ довести форму до вершины, используя API, не стесняйтесь добавлять это.
Nidonocu 9.02.2009 02:11:31
@BlueRaja, вы запускаете первый экземпляр приложения. Когда вы запускаете второй экземпляр приложения, он обнаруживает, что другой экземпляр уже запущен, и готовится к выключению. Перед этим он отправляет собственное сообщение «SHOWME» в первый экземпляр, который выводит первый экземпляр наверх. События в .NET не допускают межпроцессного взаимодействия, поэтому используется собственное сообщение.
Matt Davis 28.05.2010 21:01:30
Может быть, есть способ передать командные строки из другого экземпляра?
gyurisc 1.06.2010 06:26:31
@Nam, Mutexконструктор просто требует строку, поэтому вы можете указать любое имя строки, например «This Is My Mutex». Поскольку «Mutex» - это системный объект, который доступен другим процессам, вы обычно хотите, чтобы имя было уникальным, чтобы оно не конфликтовало с другими именами «Mutex» в той же системе. В статье загадочно выглядящая строка - «Guid». Вы можете создать это программно, позвонив System.Guid.NewGuid(). В случае статьи пользователь, вероятно, сгенерировал ее через Visual Studio, как показано здесь: msdn.microsoft.com/en-us/library/ms241442(VS.80).aspx
Matt Davis 19.10.2010 14:51:38
Предполагает ли мьютекс-подход, что один и тот же пользователь пытается снова запустить приложение? Конечно, вывод «существующего экземпляра приложения на передний план» не имеет смысла после «смены пользователя»
dumbledad 7.08.2012 14:46:23

От сюда .

Распространенное использование для межпроцессного 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.

84
23.08.2010 07:20:03
Должен сказать, мне этот ответ нравится гораздо больше, чем принятый, просто потому, что он не зависит от WinForms. Лично большая часть моей разработки перешла на WPF, и я не хочу тянуть библиотеки WinForm для чего-то подобного.
Switters 27.10.2008 18:51:12
Конечно, чтобы получить полный ответ, вы также должны описать передачу аргументов другому экземпляру :)
Simon Buchan 28.11.2008 05:32:33
@ Джейсон, хорошо, спасибо! Но я предпочитаю не пропускать тайм-аут. Это очень субъективно и зависит от множества переменных. Если вы когда-нибудь захотите включить другое приложение, просто отпустите мьютекс быстрее ... например, как только пользователь подтвердит закрытие
Eric Ouellet 5.12.2013 21:33:45
@EricOuellet: почти каждая программа, имеющая вкладки, делает это - Photoshop, Sublime Text, Chrome .... Если у вас есть веская причина иметь «основной» процесс (скажем, у вас есть встроенная БД для настроек), вы можете хочу, чтобы он показывал пользовательский интерфейс, как если бы это был новый процесс.
Simon Buchan 6.12.2013 12:09:00
@ Симон, ты прав. Я просто задаю себе вопрос об очень старой вещи ... MDI против SDI (мульти-интерфейс документа против интерфейса с одним документом). Когда вы говорите о вкладках, вы ссылаетесь на MDI. В 1998 году книга Microsoft предлагает исключить все приложения MDI. Microsoft переключила Word, Excel ... на SDI, что, на мой взгляд, проще и лучше. Я понимаю, что Chrome и другие (теперь IE) хотят вернуться к MDI. Я лично (на основании ничего / личных чувств), что еще лучше открыть новое приложение, когда выбран файл ассоциации. Но я лучше понимаю вопрос, заданный сейчас. Спасибо !
Eric Ouellet 6.12.2013 16:07:54

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

Когда я попал в 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. Поскольку в этом примере я использую C #, мне пришлось сделать ссылку на сборку. Если вы используете 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);
    }
}

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

107
19.03.2020 00:53:31
Я бы придерживался решения мьютекса, потому что оно не имеет ничего общего с формами.
Steven Sudit 13.07.2009 05:26:18
Я использовал это, потому что у меня были проблемы с другими подходами, но я уверен, что он использует удаленное взаимодействие под капотом. У моего приложения были две связанные проблемы - некоторые клиенты говорят, что оно пытается позвонить домой, хотя они сказали, что нет. Когда они выглядят более внимательно, соединение с localhost. Тем не менее, они изначально не знают этого. Кроме того, я не могу использовать удаленное взаимодействие для другой цели (я думаю?), Потому что оно уже используется для этого. Когда я попробовал мьютексный подход, я мог снова использовать удаленное взаимодействие.
Richard Watson 13.01.2011 07:47:20
Простите, но если я что-то упустил, вы избежали написания 3 строк кода и вместо этого использовали повторно фреймворк просто для написания довольно тяжелого кода для этого. Так где же экономия?
greenoldman 13.08.2011 15:31:42
это возможно сделать в winforms?
Jack 2.06.2012 04:01:23
Если вы не вызовете InitializeComponent () для экземпляра приложения, вы не сможете разрешить ресурсы ... _application = new SingleInstanceApplication (); _application.InitializeComponent (); _application.Run ();
Nick 25.07.2013 14:34:25

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

5
17.01.2016 12:17:29
«Никогда не используйте именованный мьютекс» - никогда не говори никогда. Если на моей машине запущен вредоносный код, я, вероятно, уже скрылся.
Joe 17.07.2009 20:29:39
На самом деле это даже не должен быть вредоносный код. Это может быть просто случайное столкновение имен.
Matt Davison 21.07.2009 17:52:18
Тогда что ты должен делать?
Kevin Berridge 23.10.2009 21:11:08
Лучший вопрос в том, какая возможная причина вы хотели бы такого поведения. Не проектируйте свое приложение как приложение с одним экземпляром =). Я знаю, что это слабый ответ, но с точки зрения дизайна это почти всегда правильный ответ. Не зная больше о приложении, трудно сказать гораздо больше.
Matt Davison 24.10.2009 11:32:03
По крайней мере, в Windows, Mutexes имеют контроль доступа, так что можно играть с вашим объектом. Что касается имен самих коллизий, вот почему UUID / GUID были изобретены.
NuSkooler 12.10.2010 16:59:25

Просто некоторые мысли: бывают случаи, когда требуется, чтобы только один экземпляр приложения не был «хромым», как некоторые заставили бы вас поверить. Приложения баз данных и т. Д. На порядок усложняются, если разрешить нескольким экземплярам приложения для одного пользователя доступ к базе данных (вы знаете, все это обновляет все записи, которые открываются в нескольких экземплярах приложения для пользователей машина и тд). Во-первых, для «конфликта имен» не используйте удобочитаемое имя - используйте вместо него GUID или, еще лучше, GUID + читаемое имя. Возможности столкновения имен просто выпали из радара, и Mutex это не волнует. Как кто-то указал, DOS-атака будет неудачной, но если злоумышленник попытается получить имя мьютекса и включить его в свое приложение, в любом случае вы в значительной степени являетесь целью, и вам придется сделать НАМНОГО больше, чтобы защитить себя, чем просто поиграть с именем мьютекса. Кроме того, если вы используете вариант: новый Mutex (true, «некоторый GUID плюс имя», вне AIsFirstInstance), у вас уже есть индикатор того, является ли Mutex первым экземпляром.

8
17.12.2009 21:25:51

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

Используйте это так:

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
}
20
1.02.2017 19:49:41
с этим было довольно легко начать работать. Он не закроет второе приложение, пока я не изменил Application.Exit (); на простое возвращение; но кроме этого это здорово. Хотя я признаю, что я собираюсь взглянуть на предыдущее решение ближе, так как оно использует интерфейс. blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/…
hal9000 12.08.2011 19:10:58

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

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

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

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

6
5.05.2010 00:54:24
Разве не было бы проще хранить его общую копию в классе приложения?
rossisdead 26.05.2010 04:21:31

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

15
17.01.2016 12:19:51
Я использую это с большим успехом. Если вы включите NamedPipes с этим, вы также можете передать аргументы командной строки исходному приложению. Класс SingleInstance.cs был написан Microsoft. Я добавил еще одну ссылку на более читаемую версию блога Арика Познанского на CodeProject.
Heliac 26.02.2014 12:08:41

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

public partial class App
{
    [DllImport("user32")]
    private static extern int OpenIcon(IntPtr hWnd);

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

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        var p = Process
           .GetProcessesByName(Process.GetCurrentProcess().ProcessName);
            foreach (var t in p.Where(t => t.MainWindowHandle != IntPtr.Zero))
            {
                OpenIcon(t.MainWindowHandle);
                SetForegroundWindow(t.MainWindowHandle);
                Current.Shutdown();
                return;
            }

            // there is a chance the user tries to click on the icon repeatedly
            // and the process cannot be discovered yet
            bool createdNew;
            var mutex = new Mutex(true, "MyAwesomeApp", 
               out createdNew);  // must be a variable, though it is unused - 
            // we just need a bit of time until the process shows up
            if (!createdNew)
            {
                Current.Shutdown();
                return;
            }

            new Bootstrapper().Run();
        }
    }
4
22.06.2010 00:04:21

MSDN на самом деле имеет пример приложения для C # и 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, из него должен быть выведен тип, который будет использоваться как прокладка между методом статической точки входа приложения Main и типом приложения WPF приложения. Шим определяет, когда приложение запускается впервые, и когда предпринимаются попытки последующих запусков, и дает контроль типа приложения WPF, чтобы определить, как обрабатывать запуски.

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

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

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

57
23.05.2017 12:18:22
Спасибо за то, что включили ссылку "взгляни на это тоже". Это именно то, что мне было нужно. Кстати, решение № 3 по вашей ссылке - лучшее.
Eternal21 16.11.2012 19:54:07
Я также сторонник делегирования в рамках и специально разработанные библиотеки, где это возможно.
Eniola 14.07.2016 08:42:23

Я нашел более простое решение, похожее на Дейла Рэгана, но немного измененное. Он делает практически все, что вам нужно, и на основе стандартного класса 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
4.04.2011 09:12:41

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

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);
}
10
18.01.2016 23:40:16
Это действительно хороший пример того, что мне делать. Натан, все ли аргументы отправлены с использованием этого метода? У меня есть 7 или около того в моем приложении, и я думаю, что этот код будет работать.
kevp 30.05.2012 16:05:58
В моем примере отправляется только первый аргумент, но его можно изменить, чтобы все они были отправлены.
Nathan Moinvaziri 4.06.2012 05:35:35

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

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

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

6
17.01.2016 12:24:35
Это не вывело существующее окно на передний план, когда я попробовал это.
RandomEngy 29.10.2012 02:08:59

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

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

23
15.07.2015 01:06:35
Кроме того, это не будет работать, если на вашем компьютере запущена какая-либо другая программа с таким же именем. ProcessNameвозвращает имя исполняемого файла минус exe. Если вы создадите приложение под названием «Блокнот», и блокнот Windows будет запущен, он обнаружит его как запущенное приложение.
Jcl 26.01.2015 08:59:30
Спасибо за этот ответ. Я нашел так много похожих вопросов, и ответы всегда были настолько сложными и / или запутанными, что я счел их бесполезными. Этот метод (метод № 1) прост, понятен и больше всего помог мне запустить мой код.
ElDoRado1239 30.05.2017 03:08:11

Посмотрите на следующий код. Это отличное и простое решение для предотвращения нескольких экземпляров приложения 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();
    }
}
5
17.01.2016 12:26:10

Обычно это код, который я использую для приложений 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
17.01.2016 12:30:24
Проблема этой реализации состоит в том, что вы не можете предоставить аргументы командной строки от второго экземпляра до первого. Для лучшего объяснения смотрите здесь .
Oliver 15.01.2013 12:28:22
Не похоже, что вопрос требует этого. В любом случае, это не будет последовательным поведением ... закрываемый экземпляр не должен изменять поведение существующего. Если вы хотите, чтобы ваше приложение работало иначе, вы закрываете текущий процесс и запускаете новый с другими параметрами.
Tommaso Belluzzo 21.04.2017 21:39:20
Но это поведение, как офис работает по умолчанию. Вы открываете первый документ двойным щелчком, и начинается новый процесс. Вы открываете второй документ, и он получит окно в первом экземпляре.
Oliver 24.04.2017 06:01:12
Я до сих пор не понимаю, в чем суть. Это не запрошенная функция.
Tommaso Belluzzo 24.04.2017 09:27:40
Это было. Последний параграф вопроса: код должен также информировать уже работающий экземпляр о том, что пользователь попытался запустить второй, и, возможно, также передать любые аргументы командной строки, если таковые имеются.
Oliver 24.04.2017 10:08:45

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

Он ориентирован на 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'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);
      }
    }
  }
}
6
17.01.2016 12:33:31

Код C # .NET Single Instance Application, который является ссылкой для помеченного ответа, является отличным началом.

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

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

Winforms :

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

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
    
11
16.09.2014 19:37:17

Используйте решение мьютекса:

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

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

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

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

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

1
7.08.2014 17:18:16

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

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

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

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

Класс запуска проекта должен быть установлен как «SingleInstanceApp».

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

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

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

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

WindowHelper:

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

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

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

1
15.09.2014 08:54:54

Я добавил метод 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);
}
1
26.01.2015 08:35:05

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

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

 [DllImport("user32")]
 static extern IntPtr SetForegroundWindow(IntPtr hWnd);
0
4.08.2015 06:10:24

Вот решение:

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
5.07.2016 21:14:10
Мне нравятся простые решения, поэтому я попробовал это сначала ... не смог заставить его работать.
pStan 9.08.2016 13:39:50

Вот то же самое, что реализовано через 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
11.07.2016 11:56:17

Вот так я и позаботился об этом. Обратите внимание, что отладочный код все еще там для тестирования. Этот код находится в 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
9.08.2016 14:35:08

Вот мои 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
26.08.2016 07:11:53
Что такое класс "NativeMethods"?
ufo 15.12.2016 17:21:50

Мне нравится решение, чтобы разрешить несколько экземпляров, если 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
29.09.2016 16:51:23

Не используя 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 и поставить «Приложение уже запущено».
Это может быть полезно для кого-то.

3
30.12.2016 04:30:21
Если два процесса запускаются одновременно, они могут одновременно видеть два активных процесса и завершать себя.
A.T. 19.08.2017 16:51:29
@AT Да, верно, это также может быть полезно для приложений, работающих от имени администратора или других пользователей
newbieguy 21.08.2017 22:05:20
Если вы делаете копию своего приложения и переименовываете его, вы можете запустить оригинал и копию одновременно.
Dominique Bijnens 25.01.2020 10:23:05