Запуск ограниченного количества копий программы
В комментариях к статье о запрете запуска нескольких копий программы пару лет назад был задан очень правильный вопрос: как реализовать запуск ограниченного числа копий программы больше одного? Сейчас я могу ответить на этот вопрос полноценной статьей. Настоятельно рекомендую сперва прочитать изначальную статью, чтобы не дублировать здесь теоретические выкладки. Для наглядности почти все методы остаются прежними, меняться будут только условия их использования.Первый способ использует shared-память, общую для всех копий данного приложения. В ней находится счетчик, который каждая копия увеличивает на 1 при запуске и уменьшает на 1 при завершении своей работы. При старте выполняется проверка на количество ранее запущенных копий. Счетчик достиг максимального значения - пожалуйте на выход.
Code (Assembler) : Убрать нумерацию
- ; Расшаренная секция, общая для всех копий данного приложения
- section '.shared' data readable writeable shareable
- started dd 0 ; Количество запущенных копий
- ; Сегмент кода
- section '.code' code readable executable
- cmp [started],3 ; Уже запущено три экземпляра программы?
- jae already_started ; Да, на выход
- ; +1 к счетчику копий
- ; Префикс LOCK используется для предотвращения возможных конфликтов
- ; на многопроцессорных машинах
- lock add [started],1
- ; Нормальный запуск программы
- ...
- ...
- ...
- ; -1 от счетчика копий
- lock sub [started],1
- jmp loc_exit
- already_started:
- ; Выход из программы
- ...
- loc_exit:
Второй вариант с поиском окон претерпел наибольшие изменения. Поскольку нам надо найти не одно окно, а посчитать количество окон, удовлетворяющих определенным условиям, то придется перебрать их все. Для этого используется функция EnumWindows.
Code (Assembler) : Убрать нумерацию
- ; Сегмент данных
- section '.data' data readable writeable
- title db 'Only Three Demo #2',0 ; Заголовок окна
- buff rb 100h
- count dd ? ; Счетчик найденных окон
- ;---------------------------------------------
- section '.code' code readable executable
- start:
- ; Посчитать количество окон с нужным заголовком
- mov [count],0
- invoke EnumWindows,EnumFunc,0
- ; Уже запущено максимальное количество копий?
- cmp [count],3
- jae already_started ; Да, на выход
- ; Нормальный запуск программы
- ...
- already_started:
- ; Выход из программы
- ...
Code (Assembler) : Убрать нумерацию
- proc EnumFunc hwnd:DWORD, lParam:DWORD
- ; Получить заголовок окна
- invoke GetWindowText,[hwnd],buff,100h
- ; Сравнить с нужным
- invoke lstrcmp,buff,title
- or eax,eax
- jnz @f
- ; +1 к счетчику копий
- inc [count]
- @@:
- mov eax,TRUE
- ret
- endp
Третий вариант также основан на мьютексах. Хотя казалось бы, мьютекс - уникальная сущность, он может быть в системе только в единственном экземпляре. Действительно, это так. Но нам никто не запрещает вести счетчик копий прямо в имени мьютекса. При старте программы поочередно перебираем мьютексы, если очередной мьютекс свободен, то занимаем его, иначе увеличиваем счетчик в имени. Если достигли максимального значения счетчика, то значит все свободные имена уже заняты работающими копиями приложения.
Code (Assembler) : Убрать нумерацию
- ; Сегмент данных
- section '.data' data readable writeable
- mutex db 'Only Three Demo #3_' ; Уникальное имя мьютекса
- count db '1'
- db 0
- hMutex dd ?
- ;---------------------------------------------
- section '.code' code readable executable
- loc_mutex:
- invoke CreateMutex,NULL,TRUE,mutex ; Создать мьютекс
- mov [hMutex],eax
- invoke GetLastError ; Проверить код ошибки
- cmp eax,ERROR_ALREADY_EXISTS ; Такой мьютекс уже есть?
- jne loc_ok
- invoke CloseHandle,[hMutex]
- ; Увеличить счетчик в имени мьютекса
- inc byte [count]
- ; Максимальное значение достигнуто?
- cmp byte [count],'4'
- je already_started ; Да, на выход
- jmp loc_mutex
- loc_ok:
- ; Нормальный запуск программы
- ...
- ...
- invoke CloseHandle,[hMutex]
- jmp loc_exit
- already_started:
- ; Выход из программы
- ...
- loc_exit:
Two or more processes can call CreateMutex to create the same named mutex. The first process actually creates the mutex, and subsequent processes open a handle to the existing mutex.
Это цитата из документации. Обратите внимание, что после попытки создания мьютекса, если он уже существует, обязательно вызывается команда CloseHandle, чтобы освободить открытый хэндл, хотя фактически сам хэндл нам не принадлежит. Эта же команда должна вызываться при завершении работы приложения.
Последний вариант использует виртуальные файлы, созданные функцией CreateFileMapping. Сперва у меня была мысль реализовать такой же трюк с уникальными именами, как в третьем примере с мьютексами, но потом я решил сделать что-нибудь более интересное. А именно хранить счетчик в этом виртуальном файле. Ну а что, память под него уже выделена, так хоть зря пропадать не будет. При первом запуске создается файл и в него записывается начальное значение счетчика, все последующие запуски и завершения работы будут просто менять это значение.
Code (Assembler) : Убрать нумерацию
- ; Сегмент данных
- section '.data' data readable writeable
- fname db 'Only Three Demo 4',0 ; Уникальное имя виртуального файла
- hMapping dd ?
- InstanceInfo dd ?
- ;---------------------------------------------
- section '.code' code readable executable
- start:
- ; Создать виртуальный файл
- invoke CreateFileMapping,0FFFFFFFFh,NULL,\
- PAGE_READWRITE,NULL,1024,fname
- mov [hMapping],eax
- invoke GetLastError ; Проверить код ошибки
- cmp eax,ERROR_ALREADY_EXISTS ; Такой файл уже есть?
- je @f
- ; Записать счетчик в файл
- invoke MapViewOfFile,[hMapping],FILE_MAP_ALL_ACCESS,0,0,4
- mov [InstanceInfo],eax
- mov dword [eax],1
- jmp loc_ok
- @@:
- ; Открыть виртуальный файл
- invoke OpenFileMapping,FILE_MAP_ALL_ACCESS,FALSE,fname
- mov [hMapping],eax
- ; Прочитать счетчик из файла
- invoke MapViewOfFile,[hMapping],FILE_MAP_ALL_ACCESS,0,0,4
- mov [InstanceInfo],eax
- cmp dword [eax],3
- jae already_started ; Да, на выход
- ; +1 к счетчику копий
- inc dword [eax]
- loc_ok:
- ; Нормальный запуск программы
- ...
- ...
- ; -1 от счетчика копий
- mov eax,[InstanceInfo]
- dec dword [eax]
- jmp loc_exit
- already_started:
- ; Выход из программы
- ...
- loc_exit:
В приложении примеры программ с исходными текстами для всех описанных способов. В каждом случае количество одновременно работающих копий ограничено тремя.
Просмотров: 1456 | Комментариев: 3
Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
ManHunter
(31.10.2020 в 15:11):
Пример рисовать не хочу, лениво, поясню на словах. Вместо одного DWORD в расшаренной памяти будет, например, массив из трех двордов. При запуске программа находит элемент массива с нулевым значением, через GetCurrentProcessId получает идентификатор своего процесса, записывает его в пустую ячейку. При нормальном завершении работы находит ячейку со своим идентификатором и обнуляет ее. Если все три элемента массива уже имеют ненулевые значения, то запускаемая копия, например, через OpenProcess поочередно проверяет наличие каждого процесса с этими идентификаторами. Если все три работают - на выход. Если какого-то процесса нет, значит он завершился аварийно без очистки ячейки и можно перезаписывать ячейку с его идентификатором своими данными. Как-то так.
tshbg
(31.10.2020 в 12:08):
Пример?
Andrey
(05.06.2020 в 23:47):
Код я вам скажу очень красиво реализован
Добавить комментарий
Заполните форму для добавления комментария