Как правильно организовать отслеживание запуска только одной копии приложения. |
(Мысли вслух по
многочисленным однотипным вопросам в эхоконференции)
Для отслеживания запуска второй копии приложения необходимо контролировать некий ресурс (объект), являющийся глобальным для системы и могущий быть отслеженным из другого приложения. В качестве этого могут выступать следующие ресурсы (объекты):
Рассмотрим поподробнее все перечисленные ресурсы (объекты) для организации отслеживания запуска второй копии приложения, а также достоинства и недостатки каждого из предложенных пунктов.
1.Окно приложения (заголовок окна).
Реализация
поставленной задачи данным способом основана на использовании функции FindWindow(
pointer to class name, pointer to window name).
Практически это выглядит так: в проекте первой строкой вызывается данная
функция, которая в случае успеха вернет хэндл окна . Например
var
My_win: Thandle;
begin
Application.Initialize;
My_win:=FindWindow(nil,’Заголовок окна’);
if My_win<>0 then //обнаружили копию приложения
begin
//можем послать пользовательское сообщение окну для активации
SendMessage(My_win,$8009,0,0);
//А можем это организовать другим способом (закомментировано)
{ShowWindow(My_win,SW_SHOW
);}
halt(0);
end;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
End.
Как видим, все достаточно просто, но имеет ряд НО, о которых немного позднее.
2.Класс окна.
Использование класса окна, по сути дела, является расширением первого варианта.
var
My_win: Thandle;
begin
Application.Initialize;
My_win:=FindWindow('My_formIdent',nil);
if My_win<>0 then //обнаружили копию приложения
begin
//можем послать пользовательское сообщение окну для активации
SendMessage(My_win,$8009,0,0);
//А можем это организовать другим способом (закомментировано)
{ShowWindow(My_win,SW_SHOW
);}
halt(0);
end;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
End.
Отличие состоит в том, что для поиска окна используется не заголовок окна, а класс. Совершенно очевидно, что все приложения, написанные на Delphi, будут иметь один и тот же класс. Посему его надо переопределить.
Это может выглядеть примерно так:
procedure CreateParams(var Params :TCreateParams); override;
procedure TMainForm.CreateParams(var Params :TCreateParams);
begin
inherited CreateParams(Params);
Params.WinClassName := 'My_FormIdent';
end;
На этом картину второго варианта можно считать законченной. Правда у начинающих может возникнуть вопрос – а как в приложении отловить пользовательское сообщение? Примерно так:
Опишем константу пользовательского сообщения:
const
WM_ACTIVATEFORM = $8009;
Где-нибудь в дебрях секции public опишем процедуру :
procedure WMCTIVATEFORM (var Message:TMessage ); message WM_ACTIVATEFORM ;
Ну и в теле программы саму процедуру:
procedure TMainForm.WMCTIVATEFORM (var Message:TMessage );
begin
//здесь можно написать что-нибудь на подобии
Application.Restore;
Application.BringToFront;
end;
Достоинства и недостатки:
Поскольку два первых варианта являются родственными, то и рассмотрение достоинств и недостатков разумнее будет провести вместе.
Достоинства:
Недостатки
3.Использование мьютекса.
Mutual Exclusions – Взаимоисключения. Глобальные для системы объекты, служащие для реализации механизма синхронизации (и не только).
Использование мьютекса для наших целей (отслеживание запуска второй копии приложения) не имеет ни каких отличительных особенностей, и пример файла проекта может иметь вид:
var
Mtx:
THandle;
Wait:
Longint;
{$R *.RES}
begin
Application.Initialize;
Mtx :=
CreateMutex(nil, True, 'Prog_sortmail');
try
Wait
:= WaitForSingleObject(Mtx, 0);
if
(Wait <> WAIT_TIMEOUT) then
begin
OpenMutex(SYNCHRONIZE, False, 'Prog_sortmail');
try
{--}
Application.CreateForm(TMainForm, MainForm);
Application.Run;
{---}
finally
ReleaseMutex(Mtx);
end;
end else begin
MessageDlg('Одна копия программы уже запущена!', mtError, [mbOK], 0);
Exit;
end;
finally
CloseHandle(Mtx);
end;
end.
В данном примере при обнаружении второй копии выводится сообщение. Для активации первой копии здесь может быть послано пользовательское сообщение приложению по хэндлу, найденному при помощи функции FindWindow или любым иным методом. К примеру, приложение при запуске может свой хэндл записывать в реестр или файл конфигурации.
Достоинства и недостатки:
Достоинства:
· Простота реализации.
· Возможность отслеживать запуск приложения, не имеющего окна.
· Невозможность запуска второй копии, т.к. происходит отслеживание не класса окна или его заголовка (т.е. объекта, появляющегося через некоторое время), а созданного приложением мьютекса.
Недостатки:
· Невозможность получить непосредственно хэндл окна для активации первой копии программы (хотя это может и не требоваться).
4.Использование семафора.
Semaphore – глобальный об’ект
синхронизации, имеющий счетчик для ресурсов, с ним связанных. В достаточно
грубом приближении мьютекс можно рассматривать, как частный случай семафора с
двумя состояниями.
Посему рассмотрение варианта использования семафора для отслеживания запуска второй копии приложения представляет больше академический, чем практический интерес, и рассмотрение данного варианта я позволю опустить.
5.Использование файла, отображаемого в память.
var
Hnd:hwnd;
{$R *.RES}
begin
Hnd := CreateFileMapping(HWND($FFFFFFFF),
nil,
page_read_write,
0,
1024,
'my_unicum_file_name');
if GetLastError<>ERROR_ALREADY_EXISTS
then
begin
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end;
CloseHandle(Hnd);
end.
Достоинства и недостатки:
Достоинства:
· Простота реализации.
· Возможность отслеживать запуск приложения, не имеющего окна.
Недостатки:
· Невозможность получить непосредственно хэндл окна для активации первой копии программы (хотя это может и не требоваться).
· Некоторая инерционность, связанная с использованием paging file (в случае с HWND($FFFFFFFF)).
Собственно
на этом рассмотрение вариантов отслеживания запуска второй копии приложения
можно считать законченным. Следует предостеречь начинающих от
использования различных ‘неправильных’
способов, таких, как создание файл-флагов, записей в реестр, записей в
локальные и глобальные ini-файлы и прочие
выдумки. Совершенно очевидно, что при крахе системы и ее повторном запуске эти
данные будут препятствовать запуску даже первой копии приложения, не говоря уже
о второй J.