Чтение онлайн

ЖАНРЫ

О чём не пишут в книгах по Delphi

Григорьев Антон Борисович

Шрифт:

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

способ нам не подходит.

Для решения задачи нам подойдут почтовые ящики (mailslots). Это специальные системные объекты для односторонней передачи сообщений между приложениями (ничего общего с электронной почтой эти почтовые ящики не имеют). Под сообщением здесь понимаются не сообщения Windows, а произвольный набор данных (здесь больше подходит скорее термин "дейтаграмма", а не "сообщение"). Каждый почтовый ящик имеет уникальное имя. Алгоритм отслеживания повторного запуска с помощью почтового ящика следующий. Сначала программа пытается создать почтовый ящик как сервер. Если оказывается, что такой ящик уже существует, то она подключается к нему как клиент и передает содержимое своей командной строки и завершает работу. Сервером в таком случае становится экземпляр приложения, запустившийся первым, — он-то и создаёт почтовый ящик. Остальным экземплярам останется только передать ему данные.

Примечание

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

Почтовый ящик лучше создать как можно раньше, поэтому мы будем его создавать не в методе формы, а в основном коде проекта, который обычно программист не исправляет. В результате код в dpr-файле проекта будет выглядеть так, как показано в листинге 1.48.

Листинг 1.48 Создание почтового ящика в главном файле проекта

const

 MailslotName = '\\.\mailslot\DelphiKingomSample_Viewer_FileCommand';

 EventName = 'DelphiKingdomSamplе_Viewer_Command_Event';

var

 ClientMailslotHandle: THandle;

 Letter: string;

 OpenForView: Boolean;

 BytesWritten: DWORD;

begin

 // Пытаемся создать почтовый ящик

 ServerMailslotHandle := CreateMailSlot(MailslotName, 0,

MAILSLOT_WAIT_FOREVER, nil);

 if ServerMailslotHandle = INVALID_HANDLE_VALUE then

 begin

if GetLastError = ERROR_ALREADY_EXISTS then

begin

// Если такой ящик уже есть, подключаемся к нему, как клиент

ClientMailslotHandle := CreateFile(MailslotName, GENERIC_WRITE,

FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

// В зависимости от того, какие переданы параметры, формируем

// строку для передачи предыдущему экземпляру. Первый символ

// строки - команда:

// e -
открыть файл для редактирования

// v — открыть файл для просмотра

// s — просто активизировать предыдущий экземпляр

// Для команд e и v к строке, начиная со 2-го символа,

// добавляется имя файла

if ParamCount > 0 then

begin

OpenForView := (ParamCount > 1) and

(CompareText(ParamStr(2), '/v') = 0);

if OpenForView then Letter := 'v' + ParamStr(1)

elsе Letter := 'e' + ParamStr(1);

end

else Letter := 's';

// Отправляем команду в почтовый ящик

WriteFile(ClientMailslotHandle, Letter[1], Length(Letter),

BytesWritten, nil);

// Сигнализируем об отправке данных через специальное событие

CommandEvent := OpenEvent(EVENT_MODIFY_STATE, False, EventName);

SetEvent(CommandEvent);

// Закрываем все дескрипторы

CloseHandle(CommandEvent);

CloseHandle(ClientMailslotHandle);

end;

 end

 else

 begin

// Создаем событие для сигнализирования о поступлении данных

CommandEvent := CreateEvent(nil, False, False, EventName);

// Выполняем обычный для VCL-приложений цикл

Application.Initialize;

Application.CreateForm(TDKSViewMainForm, DKSViewMainForm);

Application.Run;

// Закрываем все дескрипторы

CloseHandle(ServerMailslotHandle);

CloseHandle(CommandEvent);

 end;

end.

Теперь осталось "научить" первую копию приложения обнаруживать момент, когда в почтовом ящике оказываются сообщения, и забирать их оттуда. Было бы идеально, если при поступлении данных главная форма получала бы какое-то сообщение, но готового такого механизма, к сожалению, не существует. Из положения можно выйти, задействовав события.

Примечание

События — это объекты синхронизации, использующиеся в системе. Событие может быть взведено и сброшено. С помощью функции

WaitForSingleObject
можно перевести нить в состояние ожидания до тех пор. пока указанное событие не будет взведено. Подробное рассмотрение объектов синхронизации выходит за рамки нашей книги; они детально описаны, например, в [2].

В принципе, при использовании перекрытого ввода-вывода система может сама взводить указанное программой событие при получении данных почтовым ящиком, но перекрытый ввод-вывод имеет ограниченную поддержку в Windows 9х/МЕ и на почтовые ящики не распространяется. Чтобы приложение могло работать не только в Windows NT/2000/XP, мы не будем применять перекрытый ввод-вывод.

Поделиться с друзьями: