Який правильний спосіб створити однофазовий додаток WPF?


656

Використовуючи C # і WPF під .NET (а не Windows Forms або консоль), який правильний спосіб створити додаток, який можна запускати лише як один екземпляр?

Я знаю, що це має щось спільне з якоюсь міфічною річчю, яка називається мютекс, рідко я можу знайти когось, хто заважає зупинитися і пояснити, що з них є.

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


14
Чи CLR автоматично не випускає будь-яких неопублікованих файлів, коли програма все-таки припиняється?
Cocowalla

1
@Cocowalla: фіналізатор повинен розпоряджатися некерованими мутексами, якщо він не може знати, чи була створена файлом керована програма або приєднана до вже наявної.
Ігнасіо Солер Гарсія

Маючи лише один примірник свого додатка, розумно. Але передача аргументів вже наявній програмі здається мені трохи нерозумною. Я не бачу причин для цього. Якщо ви пов'язуєте додаток із розширенням файлу, вам слід відкрити стільки додатків, скільки користувач хоче відкрити документи. Це стандартна поведінка, якої очікували б усі користувачі.
Ерік Оуеллет

9
@Cocowalla CLR не керує власними ресурсами. Однак якщо процес припиняється, всі ручки звільняються системою (ОС, а не CLR).
Неочікуваний

1
Я вважаю за краще відповідь від @huseyint. Він використовує власний клас "SingleInstance.cs" від Microsoft, тому вам не доведеться турбуватися про Mutexes та IntPtrs. Також немає залежності від VisualBasic (yuk). Дивіться codereview.stackexchange.com/questions/20871/… для більш детальної інформації ...
Heliac

Відповіді:


537

Ось дуже гарна стаття щодо рішення Mutex. Описаний у статті підхід є вигідним з двох причин.

По-перше, він не вимагає залежності від складання Microsoft.VisualBasic. Якби мій проект вже мав залежність від цієї асамблеї, я б, напевно, виступав за використання підходу, показаного в іншій відповіді . Але як це є, я не використовую збірку Microsoft.VisualBasic, і я краще не додаю зайвої залежності до свого проекту.

По-друге, у статті показано, як вивести наявний екземпляр програми на перший план, коли користувач намагається запустити інший екземпляр. Це дуже приємний штрих, що інші рішення Mutex, описані тут, не стосуються.


ОНОВЛЕННЯ

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

Сьогодні я хотів змінити деякий код, який забороняв моїй програмі запускати кілька примірників себе.

Раніше я використовував System.Diagnostics.Process для пошуку екземпляра свого myapp.exe у списку процесів. Хоча це працює, це приносить багато накладних витрат, і я хотів чогось більш чистого.

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

У класі мого основного додатку я створив статику на ім’я Mutex :

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

Маючи ім'я mutex дозволяє нам здійснювати стек синхронізації в декількох потоках і процесах, що є просто магією, яку я шукаю.

У Mutex.WaitOne є перевантаження, яка визначає кількість часу, який ми повинні чекати. Оскільки ми насправді не хочемо синхронізувати наш код (більш просто перевіримо, чи він зараз використовується), ми використовуємо перевантаження з двома параметрами: Mutex.WaitOne (час очікування часу, bool exitContext) . Зачекайте, коли повернеться істинне, якщо воно вдається ввести, і помилкове, якщо воно не було. У цьому випадку ми зовсім не хочемо чекати; Якщо використовується наш мютекс, пропустіть його та рухаємось далі, тож ми переходимо в TimeSpan.Zero (зачекаємо 0 мілісекунд) і встановлюємо exitContext на істинний, щоб ми могли вийти з контексту синхронізації, перш ніж спробувати встановити на ньому замок. Використовуючи це, ми загортаємо наш 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 поверне помилкове значення, і ми отримаємо вікно повідомлень.

Замість того, щоб показувати вікно повідомлень, я вирішив використати трохи Win32, щоб повідомити мій запущений екземпляр, що хтось забув, що він вже працює (привівши себе до вершини всіх інших вікон). Для цього я використовував PostMessage для трансляції користувальницького повідомлення в кожне вікно (користувацьке повідомлення було зареєстроване в RegisterWindowMessage моїм запущеним додатком, а це означає, що лише моя програма знає, що це таке), тоді мій другий екземпляр виходить. Запущений екземпляр програми отримає це повідомлення та обробить його. Для цього я замінив WndProc у своїй основній формі та слухав моє спеціальне сповіщення. Коли я отримав це повідомлення, я встановив властивість TopMost форми на істинне, щоб підняти його зверху.

Ось що я закінчив:

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

5
Виходячи з того, що ця відповідь використовує менше коду та менше бібліотек та забезпечує функціональність підняття до найвищого рівня, я збираюся зробити це новою прийнятою відповіддю. Якщо хтось знає більш правильний спосіб підвести форму до вершини за допомогою API, сміливо додайте це.
Nidonocu

11
@BlueRaja, ви запускаєте перший екземпляр програми. Коли ви запускаєте другий екземпляр програми, він виявляє, що інший екземпляр вже запущений, і готується до відключення. Перш ніж це зробити, він надсилає перше інстанційне "SHOWME" нативне повідомлення, яке приводить перший екземпляр до вершини. Події в .NET не дозволяють перехресного спілкування, тому використовується нативне повідомлення.
Метт Девіс

7
Чи є спосіб передати командні рядки з іншого екземпляра, можливо?
gyurisc

22
@Nam, Mutexконструктору просто потрібна рядок, щоб ви могли вказати будь-яке ім'я рядка, наприклад, "This Is My Mutex". Оскільки "Mutex" - це системний об'єкт, доступний для інших процесів, ти зазвичай хочеш, щоб ім'я було унікальним, щоб воно не зіткнулося з іншими іменами "Mutex" у тій самій системі. У статті криптовалютний рядок - це "Керівництво". Ви можете створити це програмно, зателефонувавши System.Guid.NewGuid(). Що стосується статті, користувач, ймовірно, створив її через Visual Studio, як показано тут: msdn.microsoft.com/en-us/library/ms241442(VS.80).aspx
Метт Девіс

6
Чи передбачає підхід mutex, що той самий користувач намагається запустити програму ще раз? Безперечно виводити "існуючий екземпляр програми на перший план" не має сенсу після "користувача переключення"
dumbledad

107

Ви можете використовувати клас 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);
    }
}

Ну, я сподіваюся, ви зможете дотримуватися всього і зможете скористатися цією реалізацією та зробити її власною.


9
Я б дотримувався рішення мютексу, оскільки це не має нічого спільного з формами.
Стівен Судіт

1
Я використовував це, тому що у мене були проблеми з іншими підходами, але я впевнений, що він використовує видалення під кришкою. У мого додатка було два пов'язані проблеми - деякі клієнти кажуть, що він намагається зателефонувати додому, хоча вони сказали, що це не робити. Коли вони дивляться більш ретельно, з'єднання відбувається з localhost. Проте вони спочатку цього не знають. Крім того, я не можу використовувати видалення для іншої мети (я думаю?), Тому що воно вже використовується для цього. Коли я спробував підхід mutex, я міг знову використовувати видалення.
Річард Ватсон

4
Пробачте, але якщо я чогось не пропустив, ви уникали писати 3 рядки коду, а замість цього ви повторно використовували фреймворк просто для написання досить важкого коду, щоб це зробити. То де ж заощадження?
greenoldman

2
можна це зробити у виграшних формах?
Джек

1
Якщо ви не викликаєте InitializeComponent () в екземплярі програми, ви не зможете вирішити ресурси ... _application = new SingleInstanceApplication (); _application.InitializeComponent (); _application.Run ();
Нік

84

Від сюди .

Загальне використання для перехресного процесу 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 щось подібне.
Ковзанки

5
Звичайно, щоб бути повноцінною відповіддю, ви також повинні описати передачу аргументів до іншої інстанції :)
Simon Buchan

@Jason, добре, дякую! Але я вважаю за краще не пропускати жодного тайм-ауту. Він настільки суб'єктивний і залежить від стільки змінних. Якщо ви хочете колись увімкнути запуск іншого додатка, просто випустіть свою файлу mutex швидше .. наприклад, як тільки користувач підтвердить закриття
Eric Ouellet

@EricOuellet: Практично кожна програма, яка має вкладки, робить це - Photoshop, Sublime Text, Chrome .... Якщо у вас є вагомі підстави мати "головний" процес (скажімо, у вас є вбудований БД для налаштувань), ви можете хочу, щоб він показав користувальницький інтерфейс, ніби це теж новий процес.
Саймон Бучан

@Simon, ти маєш рацію. Я просто запитую себе про дуже стару річ ... MDI проти SDI (Multi documentinterface vs Single document interface). Коли ви говорите про вкладки, ви посилаєтесь на MDI. У 1998 році книга Microsoft пропонує усунути кожну програму MDI. Microsoft переключила Word, Excel ... на SDI, і я вважаю, що це простіше і краще. Я розумію, що Chrome та інші (зараз IE) хочуть повернутися до MDI. Я особисто (на основі нічого / особистих почуттів), що все ж краще відкрити нову програму, коли буде обраний файл assoc. Але я краще розумію питання, яке зараз задають. Дякую !
Ерік Оуеллет

58

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 Application, щоб визначити, як обробляти запуски.

  • Для C # люди просто роблять глибокий вдих і забувають про все "я не хочу включати VisualBasic DLL". Через це і те, що говорить Скотт Гензельман, і той факт, що це майже все є найчистішим рішенням проблеми, і його розробили люди, які знають набагато більше про рамки, ніж ви.
  • З точки зору зручності використання, факт полягає в тому, що якщо ваш користувач завантажує програму, і вона вже відкрита, і ви передаєте їм повідомлення про помилку, як 'Another instance of the app is running. Bye'тоді, вони не стануть дуже щасливим користувачем. Ви просто ОБОВ'ЯЗКОВО (у додатку GUI) переходите на цю програму і передаєте надані аргументи - або якщо параметри командного рядка не мають значення, тоді ви повинні спливати програму, яка, можливо, була мінімізована.

Рамка вже має підтримку цього - її просто той ідіот назвав DLL, Microsoft.VisualBasicі він не потрапив у Microsoft.ApplicationUtilsщось подібне. Перейдіть над ним - або відкрийте рефлектор.

Порада: Якщо ви використовуєте такий підхід точно так, як є, і у вас вже є App.xaml з ресурсами тощо, ви також хочете поглянути на це .


Дякуємо, що включили посилання "Подивіться також на це". Це саме те, що мені було потрібно. До речі, рішення №3 у вашому посиланні - найкраще.
Вічний21

Я також прихильник делегування в рамках та спеціально розроблених бібліотеках, де це можливо.
Еніола

23

Цей код повинен перейти до основного методу. Подивіться тут для отримання додаткової інформації про основний метод у 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
Крім того, це не працюватиме, якщо на вашому комп’ютері працює одна інша програма з таким же ім’ям. ProcessNameповертає ім'я виконуваного файлу мінус значення exe. Якщо ви зробите програму під назвою "Блокнот", а блокнот Windows працює, він визначить, що ваша програма працює.
Jcl

1
Дякую за цю відповідь. Я знайшов стільки подібних питань, і відповіді завжди були настільки витонченими та / або заплутаними, що я вважав їх марними. Цей метод (метод №1) простий, зрозумілий, і найбільше він насправді допоміг мені запустити свій код.
ElDoRado1239

20

Ну, у мене є одноразовий клас для цього, який легко працює для більшості випадків використання:

Використовуйте його так:

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/…
hal9000

15

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


Я використовую це з великим успіхом. Якщо ви включите NamedPipes з цим, ви також можете передати аргументи командного рядка в оригінальну програму. Клас "SingleInstance.cs" написав Microsoft. Я додав ще одне посилання на більш читану версію блогу Аріка Познанського на CodeProject.
Геліак

Зараз посилання розірвано.
Майк Лоурі

11

Код C # .NET одномобільний додаток, який є посиланням на марковану відповідь, - чудовий початок.

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

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

10

Ось приклад, який дозволяє мати один екземпляр програми. Коли завантажуються нові екземпляри, вони передають свої аргументи головному запущеному екземпляру.

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

Це справді приємний приклад того, що я що робити. Натане, чи всі аргументи надсилаються цим методом? У мене в додатку 7 або близько того, і я думаю, що цей код буде працювати.
кевп

1
У моєму прикладі надсилається лише перший аргумент, але його можна змінити так, що всі вони надіслані.
Натан Моінвазірі

8

Лише деякі думки: Є випадки, коли вимагає, щоб лише один екземпляр програми не був «кульгавим», як ви вважаєте, хтось. Додатки для баз даних тощо на порядок складніше, якщо можна дозволити декілька примірників програми для одного користувача отримати доступ до бази даних (ви знаєте, все це оновлення всіх записів, відкритих у декількох прикладах програми для користувачів машина тощо). По-перше, для "речі зіткнення імен" не використовуйте читане ім'я людини - замість цього використовуйте GUID або, ще краще, GUID + людське читабельне ім'я. Шанси зіткнення імені просто скинули з радара, і Mutex не хвилює Як хтось зазначав, атака DOS буде смоктати, але якщо зловмисник зіткнеться з проблемою отримати ім'я mutex та включити його у свій додаток, Ви в значній мірі ви цільові, і вам доведеться зробити МНОГО більше, щоб захистити себе, ніж просто обмацувати ім'я файлу. Крім того, якщо ви використовуєте варіант: new Mutex (вірно, "деякий GUID плюс ім'я", з AIsFirstInstance), у вас вже є ваш показник того, чи є Mutex першим екземпляром.


6

Стільки відповідей на таке, здавалося б, просте запитання. Тільки, щоб трішки трясти речі, це моє рішення цієї проблеми.

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

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

Я підняв ідею з цієї сторінки: http://www.ai.uga.edu/~mc/SingleInstance.html


3
Чи не було б простіше зберігати спільну копію цього матеріалу в класі додатків?
rossisdead

6

Схоже, існує дійсно хороший спосіб впоратися з цим:

WPF Одномоментна програма

Це забезпечує клас, який ви можете додати, який керує всім файлом mutex та обмін повідомленнями, щоб спростити вашу реалізацію до тієї точки, коли це просто тривіально.


Схоже, це не вивело існуюче вікно на перший план, коли я його спробував.
RandomEngy

6

Наведений нижче код - це моє рішення з назвою 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);
      }
    }
  }
}

5

Ніколи не слід використовувати іменований mutex для реалізації одномоментної програми (або принаймні не для виробничого коду). Шкідливий код може легко DoS ( Відмова від служби ) вашу дупу ...


8
"Ніколи не слід використовувати названий мутекс" - ніколи не кажіть ніколи. Якщо на моїй машині працює зловмисний код, я, мабуть, вже шлагувався.
Джо

Насправді це навіть не повинно бути шкідливим кодом. Це може бути просто випадкове зіткнення імені.
Метт Девісон

Тоді що вам робити?
Кевін Беррідж

Питання краще - з якої можливої ​​причини ви хочете такої поведінки. Не розробляйте додаток як додаток у єдиному екземплярі =). Я знаю, що це кульгава відповідь, але з точки зору дизайну це майже завжди правильна відповідь. Не знаючи більше про додаток, важко сказати набагато більше.
Метт Девісон

2
Принаймні під Windows, Mutexes мають контроль доступу, тому кожен може грати з вашим об’єктом. Що стосується найменування самих зіткнень, саме тому UUID / GUID є де винайдено.
NuSkooler

5

Подивіться на наступний код. Це прекрасне і просте рішення для запобігання декількох примірників програми 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

Ось що я використовую. Він поєднав перелік процесів для виконання комутації та мьютекс для захисту від "активних клікерів":

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

Я знайшов більш просте рішення, схоже на рішення Дейла Рагана, але трохи змінене. Він робить практично все необхідне і базується на стандартному класі Microsoft WindowsFormsApplicationBase.

По-перше, ви створюєте клас SingleInstanceController, який ви можете використовувати у всіх інших додатках для одного примірника, які використовують форми Windows:

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

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

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

Не передаючи аргументи (лише додаток для одного примірника), я вважаю за краще не реєструвати нове повідомлення Window і не перекривати цикл повідомлень, як визначено в Matt Davis Solution. Хоча додавати VisualBasic DLL не так вже й складно, але я вважаю за краще не додавати нове посилання лише для того, щоб робити додаток для одного примірника. Крім того, я вважаю за краще інстанціювати новий клас з 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

Не використовуючи 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заяви і помістити "Програма вже запущена".
Це може бути корисним комусь.


4
Якщо два процеси запускаються одночасно, вони можуть бачити два активні процеси і самозавершуватися.
AT

@AT Так, це також може бути корисно додаткам, які працюють як адміністратор або іншому
newbieguy

Якщо ви зробите копію вашої програми та перейменуєте її, ви можете запустити оригінал та копію одночасно.
Домінік Бійненс

2

Підходи на основі іменованих мьютексів не є кросплатформенними, оскільки названі мутекси не є глобальними в Mono. Підходи, засновані на перерахунку процесів, не мають синхронізації і можуть призвести до неправильної поведінки (наприклад, кілька запущених процесів одночасно можуть усі самозавершитись залежно від часу). Підходи, засновані на віконних системах, не бажані в консольному застосуванні. Це рішення, побудоване на відповіді Дівіна, стосується всіх цих питань:

using System;
using System.IO;

namespace TestCs
{
    public class Program
    {
        // The app id must be unique. Generate a new guid for your application. 
        public static string AppId = "01234567-89ab-cdef-0123-456789abcdef";

        // The stream is stored globally to ensure that it won't be disposed before the application terminates.
        public static FileStream UniqueInstanceStream;

        public static int Main(string[] args)
        {
            EnsureUniqueInstance();

            // Your code here.

            return 0;
        }

        private static void EnsureUniqueInstance()
        {
            // Note: If you want the check to be per-user, use Environment.SpecialFolder.ApplicationData instead.
            string lockDir = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
                "UniqueInstanceApps");
            string lockPath = Path.Combine(lockDir, $"{AppId}.unique");

            Directory.CreateDirectory(lockDir);

            try
            {
                // Create the file with exclusive write access. If this fails, then another process is executing.
                UniqueInstanceStream = File.Open(lockPath, FileMode.Create, FileAccess.Write, FileShare.None);

                // Although only the line above should be sufficient, when debugging with a vshost on Visual Studio
                // (that acts as a proxy), the IO exception isn't passed to the application before a Write is executed.
                UniqueInstanceStream.Write(new byte[] { 0 }, 0, 1);
                UniqueInstanceStream.Flush();
            }
            catch
            {
                throw new Exception("Another instance of the application is already running.");
            }
        }
    }
}

2

Я використовую Mutex у своєму рішенні для запобігання декількох випадків.

static Mutex mutex = null;
//A string that is the name of the mutex
string mutexName = @"Global\test";
//Prevent Multiple Instances of Application
bool onlyInstance = false;
mutex = new Mutex(true, mutexName, out onlyInstance);

if (!onlyInstance)
{
  MessageBox.Show("You are already running this application in your system.", "Already Running..", MessageBoxButton.OK);
  Application.Current.Shutdown();
}

1

Використовуйте розчин мютексу:

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

Ось легке рішення, яке я використовую, яке дозволяє програмі вивести вже існуюче вікно на перший план, не вдаючись до користувацьких повідомлень 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;
}

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



1

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

Мабуть, метод дози дозування працює, якщо додаток не відображається на панелі завдань, однак за допомогою методу 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

Ось те саме, що реалізовано через 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

[Я надав зразок коду для консольних та wpf-програм нижче.]

Вам потрібно лише перевірити значення createdNew змінної (приклад нижче!) Після створення іменованого екземпляра Mutex.

Булева createdNewповерне помилкову:

якщо екземпляр Mutex з назвою "YourApplicationNameHere" вже був десь створений у системі

Булева createdNewповернеться вірно:

якщо це перший Mutex під назвою "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);
}
}

1

Рішення для економії часу для C # Winforms ...

Program.cs:

using System;
using System.Windows.Forms;
// needs reference to Microsoft.VisualBasic
using Microsoft.VisualBasic.ApplicationServices;  

namespace YourNamespace
{
    public class SingleInstanceController : WindowsFormsApplicationBase
    {
        public SingleInstanceController()
        {
            this.IsSingleInstance = true;
        }

        protected override void OnStartupNextInstance(StartupNextInstanceEventArgs e)
        {
            e.BringToForeground = true;
            base.OnStartupNextInstance(e);
        }

        protected override void OnCreateMainForm()
        {
            this.MainForm = new Form1();
        }
    }

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            string[] args = Environment.GetCommandLineArgs();
            SingleInstanceController controller = new SingleInstanceController();
            controller.Run(args);
        }
    }
}

1

Перевірте запропоноване тут рішення, яке використовує семафор, щоб визначити, чи існує вже існуючий екземпляр, працює для програми WPF і може передавати аргументи від другої інстанції до першої вже запущеної інстанції, використовуючи TcpListener та TcpClient:

Він також працює для .NET Core, не тільки для .NET Framework.


1

Я не можу знайти коротке рішення тут, тож сподіваюся, що комусь сподобається це:

ОНОВЛЕНО 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;
    }

    // Optional code. If you don't want that someone runs your ".exe" with a different name:

    string exeName = AppDomain.CurrentDomain.FriendlyName;
    // in debug mode, don't forget that you don't use your normal .exe name.
    // Debug uses the .vshost.exe.
    if (exeName != "the name of your executable.exe") 
    {
        // You can add a MessageBox here if you want.
        // To point out to users that the name got changed and maybe what the name should be or something like that^^ 
        MessageBox.Show("The executable name should be \"the name of your executable.exe\"", 
            "Wrong executable name", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

    // Following code is default code:
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}

Це введе умови для перегонів. Потрібно використовувати мютекс.
georgiosd

1
немає жодної гарантії, що якщо ви закрутите два екземпляри одночасно, це буде спрацьовувати. Як і оновлення змінної з двох різних потоків. Хитрий ризикований бізнес. Використовуй силу, Лука :)
georgiosd

@georgiosd ах я бачу, що ти маєш на увазі. Як якщо б хтось запустив .exe і змінив ім’я. Так, це був би спосіб її запустити більше разів, але нормально .exe не працює, якщо ім'я було змінено. Я оновлю свою відповідь ^^ Дякую Лука: D за вказівку на це :)
Деніз

1
Не тільки це @ Деніз. Якщо ви запустили два процеси дуже швидко, є ймовірність, що список процесів або метод, що їх отримує, будуть виконуватись, поки ще є лише один. Це може бути
крайній

@georgiosd Ви можете це довести? Тому що я тестував це саме для вас. Але мені це було неможливо, навіть справді "по-справжньому швидким"! : P Тож я не можу зрозуміти, чому ти віриш у щось, що просто не так, і навіть не любиш цей невинний код: D
Деніз
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.