Блог → Запрет запуска копии приложения под Windows

Возможно, эта проблема уже успела надоесть всем и вся, однако же, далеко не каждый программист назовёт с ходу больше одного-двух вариантов её решения. Уверен, читателям моего блога будет интересно узнать, какими ещё способами можно определить наличие запущенной копии программы - и какие из них подходят именно им. Готовы? Тогда поехали!

Способ №1. Ну конечно же, старый добрый FindWindow! Это, без сомнения, первый способ, который приходит в голову. И действительно, почему бы просто не найти окно программы по его типу (заголовку) и, в случае его обнаружения, прекратить работу?

Минусов у этого решения предостаточно. Во-первых, есть вероятность того, что другое приложение зарегистрирует окно с тем же именем класса. Конечно, можно вызывать FindWindow с указанием заголовка, но для окон, имеющих динамически меняющиеся заголовки, такой способ не годится (да и элегантности решению явно не прибавляет). Во-вторых, если две копии приложения запускаются с достаточно малым интервалом между запусками, эта проверка может вообще не сработать!

Способ №2. Тут мы используем объект синхронизации Mutex. При запуске программы мы пытаемся создать его, используя уникальное имя - и, в случае, если объект уже существует, получаем ERROR_ALREADY_EXISTS.

var
MH:thandle;
key:array [0..10] of char;
MH:=CreateMuten(NIL,TRUE,Key); {пытаемся создать}
if MH <>0 then
if GetLastError=ERROR_ALREADY_EXISTS then ... {есть копия}


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

Способ №3. Практически аналогичен предыдущему, но используется объект синхронизации Semaphore.

var
SHandle:thandle;
SHandle:=CreateSemaphore(0,0,l,'mysem'); {пытаемся создать}
if SHandle <>0 then
if GetLastError = then ERROR_ALREADY_EXISTS then ... {есть копия}


Способ №4. Здесь, в том же ключе, используется Atom.

var
Fatom:Tatom;
...
if GlobalFindAtom('PROGRAM_RUNNING') = 0 {если нет атома} then
fAtom := GlobalFindAtom('PROGRAM_RUNNING') {то добавляем} else begin ... {есть копия}


Способ №5. Ну и, чтобы домучить тему до конца - файл, проецируемый в память.

var
FM:Thandle;
{пытаемся создать}
FM:=CreateFileMapping($ffffffff,nil,PAGE_READONLY,0,32,'UniqueName');
if GetLastError = ERROR_ALREADY_EXISTS then ... {есть копия}


Способ №6. В том случае, если ни один из приведённых выше способов вас не устраивает, можно попробовать вот такой - отослать всем top-level окнам самостийное сообщение. Как это сделать? При запуске программы регистрируем сообщение с помощью функции RegisterWindowMessage. Эта функция гарантирует, что сообщение будет уникальным. Если потом вызвать RegisterWindowMessage (из этого же или другого приложения) с тем же параметром, она возвратит то же значение.

var
FM_FINDMAYAPP: Integer;
FM_FindMyApp:=RegisterWindowMessage('FindMyAppMessage');


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

procedure Tform1.WndProc(var М:TMessage);
begin
if m.Msg=FM_FINDMYAPP then begin
{обработка}
end;
INHERITED WndProc(M);
end;


В принципе, это далеко не всё. Если есть желание, то можно реализовать ещё несколько алгоритмов: на основе обработки данных о состоянии системы, процессов, перебора окна, работы с shared memory. Но думаю, что всё решится раньше - одним из приведённых выше способов, и до таких извращений дело просто не дойдёт!