Еще один способ реализации Single Instance Application в .NET

Итак, возникла такая задача. На WPF пишется небольшое приложение (под .NET Framework 4 с рассчетом на пользователя Windows 7, а именно меня), а, заодно, и изучается сама технология WPF. Требуется не допускать запуска второго экземпляра приложения из-под того же пользователя.

Поиском в гугле найдено два способа реализации Single Instance Application:

1. Простейший

using System.Diagnostics;
...
//при запуске приложения
int pid = Process.GetCurrentProcess().Id;
string pname = Process.GetCurrentProcess().ProcessName;
foreach(var p in Process.GetProcesses())
 if(p.ProcessName==pname && p.Id != pid)
 Application.Current.Exit(0);


Недостатки очевидны — если имя экзешника не слишком мудреное, есть вероятность совпадений, — это во-первых, а во-вторых — что делать, если такой процесс уже запущен из-под другого пользователя (все-таки Windows NT допускает несколько одновременных сеансов разных юзеров). Думаю, вторая проблема в принципе разрешима, но все равно получается как-то некрасиво.

2. С мьютексом
Опытным путем установлено, что в Windows 7 под 4-м .NET Framework не работает. Плюс, если бы и работал — были бы проблемы с повторным запуском приложения после его аварийного завершения. Так что код приводить не буду.

Поэтому пришлось искать свое решение. Итак — от одного пользователя может быть запущен только один экземпляр приложения:

public partial class App : Application
 {
 private const string MYAPPNAME = "WLaunchPad-734685-3487563456-345734564";

private void Application_Startup(object sender, StartupEventArgs e)
 {
 var cp = Process.GetCurrentProcess();
 var cpid = cp.Id;
 var cpname = cp.ProcessName;
 var regpath = Registry.CurrentUser.CreateSubKey("Software", RegistryKeyPermissionCheck.ReadWriteSubTree).
 CreateSubKey(MYAPPNAME, RegistryKeyPermissionCheck.ReadWriteSubTree);
 regpath.SetValue(cpid.ToString(), "running");
 bool stopflag = false;
 foreach (string v in regpath.GetValueNames())
 {
 int id = Convert.ToInt32(v);
 Process proc = null;
 try
 {
 proc = Process.GetProcessById(id);
 }
 catch (ArgumentException)
 {
 regpath.DeleteValue(v);
 }
 if (proc == null)
 continue;
 if (proc.Id != cpid && proc.ProcessName == cpname)
 Application.Current.Shutdown();
 }
 }

private void Application_Exit(object sender, ExitEventArgs e)
 {
 var regpath = Registry.CurrentUser.CreateSubKey("Software", RegistryKeyPermissionCheck.ReadWriteSubTree).
 CreateSubKey(MYAPPNAME, RegistryKeyPermissionCheck.ReadWriteSubTree);
 try
 {
 var cpid = Process.GetCurrentProcess().Id;
 string valname = cpid.ToString();
 if (regpath.GetValueNames().Contains(valname))
 regpath.DeleteValue(valname);
 }
 catch { }
 }

Этот код делает следующее. В HKCU\Software создается параметр реестра с нетривиальным именем.
При запуске программы у этого параметра создается значение, имя которого представляет собой строковую запись PID текущего процесса. Затем все значения проверяются на соответствие запущенным процессам, если процесс отсутствует, соответствующее значение удаляется (на случай, если в прошлый раз программа была завершена аварийно). Если же процесс существует, и его PID не равен PID текущего процесса, приложение завершается. При нормальном же завершении программы значение в реестре, соответствующее текущему процессу, также удаляется. Следует отметить, что при вызове Application.Shutdown(), использованном в этом коде, событие ApplicationExit также происходит, в результате чего при попытках повторного запуска программы у нашего параметра реестра не образуются лишние мусорные значения.


0 комментариев

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