Обнаружение, если другой экземпляр приложения уже запущен

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

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

Например:

  • Загружается экземпляр 1, получает мьютекс.
  • Загружает экземпляр 2, не может получить мьютекс, знает другой экземпляр. Пока что так хорошо.
  • Экземпляр 1 закрывается, высвобождает мьютекс.
  • Экземпляр 3 загружает, получает мьютекс, не знает, что экземпляр 2 все еще запущен.

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

(С#, настольное приложение)

Изменить: Чтобы уточнить, приложение не обязательно должно быть ограничено одним экземпляром, просто выполните несколько другое стартовое действие, если другой экземпляр уже запущен. Несколько экземпляров являются точными (и ожидаемыми).

Ответы

Ответ 1

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

EDIT: обновил код, чтобы автоматически определить название приложения.

using System;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;

static void Main()
{
    if (!EnsureSingleInstance())
    {
        return;
    }

    //...
}

static bool EnsureSingleInstance()
{
    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);
        SetForegroundWindow(runningProcess.MainWindowHandle);

        return false;
    }

    return true;
}

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

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

private const int SW_SHOWMAXIMIZED = 3;

Ответ 2

Другой подход - обнаружить исполняемый экземпляр, как описано в блог Scott Hanselman

Его пример активирует первый экземпляр, когда второй пытается.

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

Ответ 3

Попробуйте использовать Семафор вместо Mutex

Ответ 4

Не удалось ли просто проверить GetLastError() после создания мьютекса с помощью CreateMutex()? Если он возвращает ERROR_ALREADY_EXISTS, то есть еще один исполняемый экземпляр вашего приложения.

Согласно http://msdn.microsoft.com/en-us/library/ms682411%28VS.85%29.aspx,

Если мьютекс является именованным мьютеком и объект существовал до этой функции call, возвращаемое значение является дескриптором существующий объект, GetLastError возвращает ERROR_ALREADY_EXISTS, bInitialOwner игнорируется, и вызывающий поток не предоставляется владение. Однако, если вызывающий абонент ограниченные права доступа, функция сбой ERROR_ACCESS_DENIED и вызывающий должен использовать OpenMutex функция.

EDIT: Просто понял, что это вопрос С#/. Net, извините.

В .Net используйте конструктор Mutex, который возвращает созданный флаг New, http://msdn.microsoft.com/en-us/library/bwe34f1k%28VS.80%29.aspx:

public Mutex (
    bool initiallyOwned,
    string name,
    out bool createdNew
)

Ответ 5

хороший подход - использовать решение Sandor, но использовать WMI для получения списка процессов, описанного здесь: С#: как получить полный путь к запуску процесса? (Решение Джеффа). Таким образом, вы также можете проверить, соответствуют ли другие запущенные экземпляры по пути и идентификатору сеанса удаленного терминала:

    static bool EnsureSingleInstance()
    {
        Process currentProcess = Process.GetCurrentProcess();

        var wmiQueryString = "SELECT ProcessId, ExecutablePath, CommandLine FROM Win32_Process";
        using (var searcher = new ManagementObjectSearcher(wmiQueryString))
        using (var results = searcher.Get())
        {
            var query = from p in Process.GetProcesses()
                        join mo in results.Cast<ManagementObject>()
                        on p.Id equals (int)(uint)mo["ProcessId"]
                        select new
                        {
                            Process = p,
                            Path = (string)mo["ExecutablePath"],
                            CommandLine = (string)mo["CommandLine"],
                        };

            var runningProcess = (from process in query
                                  where
                                    process.Process.Id != currentProcess.Id &&
                                    process.Process.ProcessName.Equals(
                                      currentProcess.ProcessName,
                                      StringComparison.Ordinal) &&
                                      process.Path == currentProcess.MainModule.FileName &&
                                      process.Process.SessionId == currentProcess.SessionId
                                  select process).FirstOrDefault();

            return runningProcess == null;
        }
    }