Книги: [Классика] [Базы данных] [Internet/WWW] [Сети] [Программирование] [UNIX] [Windows] [Безопасность] [Графика] [Software Engineering] [ERP-системы] [Hardware]
Способы обнаружения копии уже загруженной программы
(статья была опубликована в журнале "Программист")
В третьем номере журнала "Программист" за 2002 год была опубликована статья Андрея Нефедова "Запрет запуска копии приложения под Windows". Перечисленные в ней способы обнаружения уже запущенной копии программы сводились к одному - созданию какого-либо глобального именного объекта и последующей проверки его наличия. Основной недостаток такого подхода - отсутствие гарантий уникальности глобальных имен. Одно и то же имя могут использовать две различные программы, что приведет к очевидному конфликту - вторая не станет запускаться, если активная первая.
С другой стороны, к запрету на одновременное использование двух копий приложения, следует подходить чрезвычайно осторожно, - не создаст ли это ограничение неудобств для пользователя? По крайней мере, следует предусмотреть возможность совместной работы различных версий одного и того же приложения.
Распознать повторный запуск приложения можно тривиальным просмотром списка загруженных процессов - Windows запоминает имя и полный путь к файлу, породившему процесс. Это гарантированно исключает возможность конфликтов со всеми остальными приложениями, но чувствительно к переименованию и перемещению файлов, - в этом случае повторный запуск программы не будет распознан.
Но при ближайшем рассмотрении такой недостаток оборачивается достоинством - пользователь получает возможность одновременной работы с различными версиями одного и того же приложениями, установленными в "свои" каталоги!
Получить список процессов легче всего средствами TOOLHELP32. Для этого сначала необходимо вызовом CreateTollhelp32snapshot снять "слепок" состояния процессоров, а затем передать возращенный ею дескриптор функциям Process32First и Process32Next. Каждый процесс, в свою очередь, ассоциирован с одним или несколькими модулями, список которых можно получить с помощью функций Module32First и Module32Next, предварительно сделав "слепок" состояния владеющего ими процесса вызовом CreateTollhelp32snapshot.
Ниже приведен исходный текст программы, выводящий на экран полные имена файлов, загруженных или исполняющихся в данный момент. Имя же самой программы передается ей в нулевом аргументе командной строки. Программа прекрасно работает как под Windows 9x, так и под Windows NT\2000.
#include <stdio.h>
#include <windows.h>
#include <tlhelp32.h>
// заголовочный файл библиотеки TOOLHELP32
// функция выводит список модулей, ассоцированных
// с данным процессом
void GetModuleList(DWORD th32ProcessID)
{
HANDLE h;
MODULEENTRY32 mdl;
// создание "слепка" состояния всех
// модулей процесса th32ProcessID
h=CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
th32ProcessID);
// для инициализации структуры MODULEENTRY32
// необходимо указать ее размер
mdl.dwSize=sizeof(MODULEENTRY32);
// получение сведений о первом модуле в списке
Module32First(h,&mdl);
while(1)
{
// в поле szExePath содержится полное имя
// файла-модуля
printf("\tszExePath\t-\t%s\n",mdl.szExePath);
// получение сведений о всех остальных модулях
// или выход, если конец
if (!Module32Next(h,&mdl)) break;
}
// уничтожение "слепка"
CloseHandle(h);
}
main()
{
HANDLE h;
PROCESSENTRY32 pe;
// создание "слепка" состояния всех процессов
// второй аргумент при "слепке" состояния процессов
// игнорируется и может принимать какие угодно
// значения
h=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,NULL);
// для инициализации структуры PROCESSENTRY32
// необходимо указать ее размер
pe.dwSize=sizeof(PROCESSENTRY32);
// получение сведений о первом процессе в списке
Process32First(h,&pe);
while(1)
{
// в поле szExePath содержится имя исполняемого
// файла, ассоциированного с процессом, без пути
printf("szExeFile\t-\t%s\n",pe.szExeFile);
// в поле th32ProcessID содержится идентификатор
// процесса, с помощью которого можно получить
// список модулей данного процесса.
// Это необходимо для выяснения полного имени
// исполняемого файла
GetModuleList(pe.th32ProcessID);
// получение сведений о следующем процессе
// или выход, если конец
if (!Process32Next(h,&pe)) break;
}
return 0;
}
Листинг 8 Демонстрация отслеживания уже запущенной копии программы путем просмотра списка процессов средствами TOOLHELP
Другой способ предотвращения повторной загрузки программы состоит в запрещении совместного доступа к файлам, используемым программой. Этот метод хорош тем, что не требует от разработчика никаких дополнительных усилий, достаточно лишь не закрывать некоторый файл на протяжении всего сеанса работы. Во избежании возникновения конфликтов с другими приложениями, этот файл должен быть расположен в каталоге, в котором установлена программа, или находиться в одном из его подкаталогов.
Демонстрационный пример реализации приведен ниже.
#include <stdio.h>
#include <windows.h>
#include <winbase.h>
main()
{
char buff[2];
HANDLE h;
// Создание временного файла
h=CreateFile("my",GENERIC_WRITE,
NULL,NULL,CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_DELETE_ON_CLOSE,
NULL);
// если открытие прошло успешно – нормальная работа
if (h!=INVALID_HANDLE_VALUE)
printf("Hello, Sailor!\n");
else
// если возникла ошибка совместного доступа,
// следовательно, приложение уже было запущено
if (GetLastError()==ERROR_SHARING_VIOLATION)
{
printf("Нельзя запускать более"
"одной копии приложения!\n");
return;
}
// ожидание нажатие на <enter>
fgets(&buff[0],2,stdin);
}
Листинг 9 Демонстрация отслеживания уже запущенной копии программы путем блокирования совместного доступа к дисковому файлу
Аккуратно написанная программа должна не просто завершать свою работу, обнаружив уже загруженную копию, но и переключать эту копию на передний план, - так работают практически все "фирменные" приложения. Для осуществления такой операции необходимо знать дескриптор главного окна программы.
Выяснить это поможет широковещательная рассылка сообщений. Передав функции SendMessage в качестве первого аргумента константу HWND_BROADCAST, можно заставить ее посылать сообщения всем top-level окнам. В сообщении следует указать уникальный код, позволяющий безошибочно идентифицировать данное приложение (см. врезку "пути достижения уникальности").
Получив такое сообщение, программа должна проанализировать идентификатор и, в зависимости от результата операции, либо проигнорировать это сообщение, либо переключиться на передний план и возвратить обратный ответ, приказывая своей копии завершить работу.
Готовая реализация по причине экономии места здесь не приводится (как и все оконные Windows-приложения она слишком громоздка). Достаточно лишь отметить, что перед посылкой широковещательного сообщения, необходимо предварительно зарегистрировать это сообщение вызовом RegisterWindowMessage. Сам же отправитель широковещательного сообщения не получает.
Если требуется ограничить количество одновременно работающих копий одного и того же приложения, можно воспользоваться динамической ветвью реестра HKEY_DYN_DATA, создав в "своем" разделе специальную переменную увеличивающуюся на единицу при очередном запуске и уменьшающуюся при выходе из приложения. Почему необходимо использовать именно динамическую ветвь? Дело в том, что если работа программы будет аварийно завершена (например, погаснет свет или произойдет критическая ошибка), счетчик запущенных приложений, расположенный в статичной ветке реестра, окажется не обнулен! Напротив, HLEY_DUN_DATA хранится в оперативной памяти и очищается при каждой перезагрузке операционной системы. По этой же причине, никогда не следует хранить счетчик запущенных приложений во временном файле! (Разве, на виртуальном диске)
Способов обнаружения ранее запушенных копий программ существует очень много - всех не перечислишь! Поэтому, не следует "зацикливаться" на одном FindWindow – подходите к решению каждой задачи творчески. Экспериментируйте, ищите собственные методы! Уверяю, они есть. На последок еще один довольно неожиданный способ, основанный на буфере обмена.
Если приложение регистрирует свой собственный формат данных (как часто и бывает), передавая его название функции RegisterClipboardFormat, это может служить отличным индикатором наличия уже запущенной копии приложения!
Причем, после выхода из приложения зарегистрированный формат не удаляется и не существует функций API, удаляющих его "в ручную". Это обстоятельство можно использовать для создания программ, рассчитанных всего лишь на один запуск на каждый сеанс работы с Windows. Использование динамической ветви реестра дает не худший результат за одним небольшим исключением - манипуляции с реестром элементарно отслеживают даже неопытные пользователи, вооруженные мониторами реестра, и при желании они смогут легко заставить приложение запуститься повторно, удалив из реестра соответствующую запись.
Замечание: кстати, ограничение "однократный запуск на весь сеанс работы с Windows" очень хорошо подходит для Shareware-программ. Такая защита чрезвычайно проста в реализации и, в то же время, не может быть обманута рядовым пользователем. Конечно, профессиональных взломщиков она не остановит, но в большинстве случаев этого и не требуется!
Пути достижения уникальности: прежде чем использовать способы, предложенные Нефедовым в его статье, необходимо научиться формировать уникальные имена, гарантированно не используемые никакими другими приложениями.
Первое, что приходит на ум, - преобразовывать полное имя запускаемого файла к строке символов, не содержащей недопустимых в глобальных именах знаков обратной наклонной черты и двоеточия. Например, можно заменить их символом прочерка. Полученное имя гарантированно не станет конфликтовать ни с какими другими приложениями, но, однако, не сможет распознать переименование или перенос запускаемого файла приложения.
Второй способ заключается в использовании генератора случайных чисел, с помощью которого формируется уникальное имя до компиляции приложения. В среду разработки Microsoft Visual Studio входит удобная и компактная утилита UUIDGEN, генерирующая 128 битный уникальный идентификатор. Вообще-то она предназначается для создания неконфликтных идентификаторов интерфейсов OLE и ActiveX объектов, но ничуть не хуже подходит и для нашего случая.
Аннотация
Статьи из книги
[Заказать книгу в магазине "Мистраль"]