Как правильно организовать отслеживание запуска только одной копии приложения.

 

(Мысли вслух по многочисленным однотипным вопросам в эхоконференции)

Vladimir Dobrachev 

 

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

  1. Окно приложения (точнее заголовок окна)
  2. Класс окна
  3. Мьютекс
  4. Семафор
  5. Файл, отображаемый в память

 

Рассмотрим поподробнее все перечисленные ресурсы (объекты) для организации отслеживания запуска второй копии приложения, а также достоинства и недостатки каждого из предложенных пунктов.

 

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.