Базы данныхИнтернетКомпьютерыОперационные системыПрограммированиеСетиСвязьРазное
Поиск по сайту:
Подпишись на рассылку:

Назад в раздел

Ограничение количества одновременно запущенных экземпляров приложения

div.main {margin-left: 20pt; margin-right: 20pt}

Ограничение количества одновременно запущенных экземпляров приложения


Владимир Юдин, Мастера Delphi


Обсуждение данной темы ведется, начиная с появления первых 32-х разрядных версий Windows. Казалось бы, проблема давно уже должна быть окончательно решена, но количество вопросов в конференциях и форумах не уменьшается, хотя из книги в книгу, из FAQ'а в FAQ кочуют одни и те же варианты решения. Но не все так очевидно и просто, что подтверждает и вынесенное в эпиграф мнение известного авторитета и эксперта.

Определим требования, которым должно удовлетворять решение для того, что бы его можно было использовать в большинстве случаев. Очевидно, что способ должен: работать во всех версиях ОС; быть надежным; достаточно универсальным; простым в реализации; не давать побочных эффектов.

Каким образом можно поступить? На ум сразу приходит решение «в лоб»: почему бы не просмотреть список всех запущенных в системе процессов и не определить, запущен ли уже наш исполняемый модуль? Но почему-то именно этот «очевидный» способ практически никогда не используется. Для получения списка процессов в Windows 9X используются функции ToolHelp, а в Windows NT – PSAPI. То есть для разных версий Windows и алгоритмы разные. Оставим этот способ для отладочных средств и просмотрщиков…

Довольно распространена проверка на наличие окна с известным заголовком. Способ хорош тем, что если такое окно существует, то оно выводится на передний план. Однако и тут существуют некоторые неприятные особенности, ограничивающие применение способа. А именно, возможна реальная ситуация, когда между процедурой проверки и созданием окна происходит довольно длительная инициализация. Экземпляр приложения, запущенный в этот промежуток времени, не обнаружит окна и запуститься. К сказанному следует добавить, что способ не будет работать в приложениях, меняющих заголовок окна и вообще окон не создающих.

Раз «в лоб» нельзя, то остается стандартный способ – при запуске приложения проверить какой-то уникальный признак и, если он не установлен, то установить, а после завершения работы сбросить. Именно так на практике обычно и поступают. Кандидатов на роль уникального признака довольно много, но не все одинаково пригодны для нашей задачи. Например, не лучшим решением будет использовать глобальные атомы. В случае аварийного завершения приложения, когда явно не вызывается GlobalFreeAtom, они имеют неприятную особенность оставаться в системе до ее перезагрузки. По той же причине абсолютно неприемлемы: создание или попытка открытия какого-либо файла, использование реестра и т.п. Лучшими кандидатами остаются объекты ядра – они просты в использовании, быстры, счетчик ссылок на объект, как правило, декрементируется даже при аварийном завершении процесса. Чаще всего это Mutex и FileMapping.

Типичный код выглядит так: program MyProgram; uses Windows, Forms, MyUnit in 'MyUnit.pas' {Form1}; {$R *.RES} var Mutex : THandle; begin Mutex := CreateMutex(nil, False, ‘MyMutex’); if Mutex = 0 then MessageBox(0,'Невозможно создать мьютекс', 'Ошибка', MB_OK or MBICONSTOP) else if GetLastError = ERROR_ALREADY_EXISTS then MessageBox(0,'Программа уже запущена', 'Ошибка', MB_OK or MBICONSTOP) else begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; CloseHandle(Mutex); end; end.

В приведенном коде все же есть один дефект. Он никогда не проявится в приложениях типа «Hello World!”, но может привести к очень неприятным последствиям в приложениях серьезных. Я имею в виду инициализацию, которая происходит до процедуры проверки при загрузке модулей. Она так же может занимать достаточно много времени. В этих случаях также возможен запуск нескольких экземпляров приложения, что в некоторых случаях может привести к конфликтам доступа и нехватке ресурсов (из-за чего, собственно говоря, чаще всего проверка и производится). Даже если этого и не произойдет, производится бесполезная работа. Отсюда вывод: процедуру проверки необходимо производить как можно раньше, до начала всех инициализаций, и в случае неудачи немедленно завершать приложение. Для этого помещаем процедуру проверки в отдельный модуль, который указываем в списке используемых проектом модулей ПЕРВЫМ:

program MyProgram; uses OneHinst; Windows, Forms, MyUnit in 'MyUnit.pas' {Form1}; {$R *.RES} begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.Run; end. unit OneHinst; interface implementation uses Windows; var Mutex : THandle; MutexName : array[0..255] of Char; function StopLoading : boolean; var L,I : integer; begin // В качестве уникального имени мьютекса используем полный путь // к исполняемому файлу приложения L := GetModuleFileName(MainInstance,MutexName,SizeOf(MutexName)); // В имени мьютекса нельзя использовать обратные слэши, поэтому // заменяем их на прямые for I := 0 to L - 1 do if MutexName[I] = '' then begin MutexName[I] := '/'; end; Mutex := CreateMutex(nil,false,MutexName); Result := (Mutex = 0) or // Если мьютекс не удалось создать (GetLastError = ERROR_ALREADY_EXISTS); // Если мьютекс уже существует end; procedure ShowErrMsg; const PROGRAM_ALREADY_RUN = 'Невозможно запустить программу'; begin MessageBox(0,PROGRAM_ALREADY_RUN,MutexName, MB_ICONSTOP or MB_OK); end; initialization if StopLoading then begin ShowErrMsg; // Так как никаких инициализаций еще не производилось, то // спокойно используем для завершения программы Halt - // finalization все равно выполнится halt; end; finalization if Mutex 0 then CloseHandle(Mutex); end.

Напоследок еще пример: как ограничить количество одновременно исполняющихся экземпляров приложения

unit LimHinst; //**************************************************************************** // // Author: ©2001 Vladimir G. Yudin aka y-soft // e-mail: y-soft@mail.ru // // Description: Ограничение количества одновременно работающих экземпляров // приложения. // // Отличие от существующих реализаций: // // 1. С целью исключения преждевременных инициализаций проверка // производится в самом начале загрузки приложения, до загрузки всех модулей // 2. Исключен возможный конфликт имен, т.к. в качестве уникального имени // используется полный путь к исполняемому модулю // 3. Изменением значения HinstLim можно установить любое разрешенное количество // одновременно запущенных экземпляров приложения // 4. Изменением WaitPause можно регулировать время ожидания // (если установить INFINITE, то получится своеобразный вариант горячего // резервирования) // // Тестировалось в WinME и WinNT 4 SP6A. Ошибки не обнаружены // // Usage: модуль необходимо указать ПЕРВЫМ в списке uses файла .DPR проекта // и установить необходимые значения констант HinstLimit и WaitPause. // // Возможные расширения: // // 1. Значения HinstLimit и WaitPause хранить в INI-файле или в реестре // 2. Значение HinstLimit менять динамически в зависимости от условий // // Thanks: Спасибо Юрию Зотову за указание на существование проблемы // // Disclaimer: Используйте совершенно свободно на свой страх и риск. // Автор убедительно просит сообщать ему о найденных ошибках и // внесенных усовершенствованиях. // Всякие совпадения идей, наименований функций, процедур, переменных и // констант считать случайными :) // //**************************************************************************** interface const //Установите необходимые значения!!! HinstLimit = 1; WaitPause = 50; implementation uses Windows; var Semaphore : THandle; SemaphoreName : array[0..255] of Char; IncCnt : integer; function StopLoading : boolean; var L,I : integer; begin // В качестве уникального имени семафора используем полный путь // к исполняемому файлу приложения (по определению уникален!!!) L := GetModuleFileName(MainInstance,SemaphoreName,SizeOf(SemaphoreName)); // В имени семафора нельзя использовать обратные слэши, поэтому // заменяем их на прямые (или еще на что-нибудь кроме #0) for I := 0 to L - 1 do if SemaphoreName[I] = '' then SemaphoreName[I] := '/'; Semaphore := CreateSemaphore(nil,HinstLimit,HinstLimit,SemaphoreName); Result := (Semaphore = 0) or // Если семафор не удалось создать (WaitForSingleObject(Semaphore,WaitPause) WAIT_OBJECT_0); // Если семафор занят end; procedure ShowErrMsg; const PROGRAM_ALREADY_RUN = 'Лимит исчерпан'; begin // Главное окно программы еще не существует, поэтому выводим MessageBox // без владельца MessageBox(0, PROGRAM_ALREADY_RUN, SemaphoreName, MB_ICONSTOP or MB_OK); end; initialization IncCnt := 0; if StopLoading then begin ShowErrMsg; // Так как никаких инициализаций еще не производилось, то // спокойно используем для завершения программы Halt - // finalization все равно выполнится halt; end else IncCnt := 1; finalization if Semaphore 0 then begin // Обязательно явно освобождаем семафор, т.к. // автоматически его счетчик ссылок не переустанавливается ReleaseSemaphore(Semaphore, IncCnt, nil); // Напоследок во избежание неожиданностей освобождаем дескриптор семафора // (так предписывает MSDN) CloseHandle(Semaphore); end; end.

Автор благодарит всех, принявших участие в обсуждении на форуме http://delphi.mastak.ru/cgi-bin/forum.pl?look=1&id=1000482255&n=0 (копия форума)

  

Внимание! Запрещается перепечатка данной статьи или ее части без согласования с автором. Если вы хотите разместить эту статью на своем сайте или издать в печатном виде, свяжитесь с автором.
Автор статьи:  Владимир Юдин

  


  • Главная
  • Новости
  • Новинки
  • Скрипты
  • Форум
  • Ссылки
  • О сайте




  • Emanual.ru – это сайт, посвящённый всем значимым событиям в IT-индустрии: новейшие разработки, уникальные методы и горячие новости! Тонны информации, полезной как для обычных пользователей, так и для самых продвинутых программистов! Интересные обсуждения на актуальные темы и огромная аудитория, которая может быть интересна широкому кругу рекламодателей. У нас вы узнаете всё о компьютерах, базах данных, операционных системах, сетях, инфраструктурах, связях и программированию на популярных языках!
     Copyright © 2001-2019
    Реклама на сайте