А. Григорьев - О чём не пишут в книгах по Delphi
- Название:О чём не пишут в книгах по Delphi
- Автор:
- Жанр:
- Издательство:БХВ-Петербург
- Год:2008
- Город:СПб
- ISBN:978-5-9775-019003
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
А. Григорьев - О чём не пишут в книгах по Delphi краткое содержание
Рассмотрены малоосвещённые вопросы программирования в Delphi. Описаны методы интеграции VCL и API. Показаны внутренние механизмы VCL и приведены примеры вмешательства в эти механизмы. Рассмотрено использование сокетов в Delphi: различные механизмы их работы, особенности для протоколов TCP и UDP и др. Большое внимание уделено разбору ситуаций возникновения ошибок и получения неверных результатов в "простом и правильном" коде. Отдельно рассмотрены особенности работы с целыми, вещественными и строковыми типами данных, а также приведены примеры неверных результатов, связанных с ошибками компилятора, VCL и др. Для каждой из таких ситуаций предложены методы решения проблемы. Подробно рассмотрен синтаксический анализ в Delphi на примере арифметических выражений. Многочисленные примеры составлены с учётом различных версий: от Delphi 3 до Delphi 2007. Прилагаемый компакт-диск содержит примеры из книги.
Для программистов
О чём не пишут в книгах по Delphi - читать онлайн бесплатно ознакомительный отрывок
Интервал:
Закладка:
<���Имя программы> "%1"
Если существуют разные варианты запуска одного файла (т.е. как в нашем случае — open и view), они различаться дополнительным параметрами. В частности, в нашем примере для открытия для редактирования не будут требоваться дополнительные параметры, для открытия для просмотра в качестве второго параметра должен передаваться кляч v, т.е. в реестр для этой команды будет записана такая строка:
<���Имя программы> "%1" v
Программа должна анализировать переданные ей параметры и открывать соответствующий файл в требуемом режиме. В нашем случае этот код выглядит очень просто (листинг 1.47).
procedure TDKSViewMainForm.FormShow(Sender: TObject);
var
OpenForView: Bооlean;
begin
// Проверяем наличие ключа "/v" в качестве второго параметра
OpenForView := (ParamCount > 1) and (CompareText(ParamStr(2), '/v') = 0);
if ParamCount > 0 then OpenFile(ParamStr(1), OpenForView);
...
end;
B более сложных случаях (например, при большем числе команд для ассоциированного файла) анализ командной строки будет сложнее, но его принципы останутся теми же.
1.3.2.3. Поиск уже запущенной копии приложения
Во многих случаях желательно не давать пользователю возможности запустить второй экземпляр вашего приложения. В 16-разрядных версиях Windows все приложения выполнялись в одной виртуальной машине, и каждому из них через переменную HPrevInstance
передавался дескриптор предыдущей копии. По значению HPrevInstance
программа легко могла найти свой предыдущий экземпляр или определить, что других экземпляров нет, если HPrevInstance
равна нулю. В 32-разрядных версиях эта переменная для совместимости оставлена, но всегда равна нулю, т.к. предыдущая копия работает в своей виртуальной машине, и ее дескриптор не имеет смысла. Альтернативного механизма обнаружения уже запущенной копии система не предоставляет, приходится выкручиваться своими силами.
Для обнаружения уже запущенного приложения многие авторы предлагают использовать именованные системные объекты (мьютексы, семафоры, атомы и т.п.). При запуске программа пытается создать такой объект с определенным именем. Если оказывается, что такой объект уже создан, программа "понимает", что она — вторая копия, и завершается. Недостаток такого подхода — с его помощью можно установить только сам факт наличия предыдущей копии, но не более того. В нашем случае задача шире: при запуске второго экземпляра приложения должен активизироваться первый, а если второму экземпляру была передана непустая командная строка, первый должен получить эту строку и выполнить соответствующее действие, поэтому описанный способ нам не подходит.
Для решения задачи нам подойдут почтовые ящики (mailslots). Это специальные системные объекты для односторонней передачи сообщений между приложениями (ничего общего с электронной почтой эти почтовые ящики не имеют). Под сообщением здесь понимаются не сообщения Windows, а произвольный набор данных (здесь больше подходит скорее термин "дейтаграмма", а не "сообщение"). Каждый почтовый ящик имеет уникальное имя. Алгоритм отслеживания повторного запуска с помощью почтового ящика следующий. Сначала программа пытается создать почтовый ящик как сервер. Если оказывается, что такой ящик уже существует, то она подключается к нему как клиент и передает содержимое своей командной строки и завершает работу. Сервером в таком случае становится экземпляр приложения, запустившийся первым, — он-то и создаёт почтовый ящик. Остальным экземплярам останется только передать ему данные.
В случае аварийного завершения программы система сама закроет все открытые ею дескрипторы, поэтому даже если первая копия будет снята системой и не сможет корректно закрыть дескриптор почтового ящика, ящик будет уничтожен и не помешает пользователю запустить новую копию программы.
Почтовый ящик лучше создать как можно раньше, поэтому мы будем его создавать не в методе формы, а в основном коде проекта, который обычно программист не исправляет. В результате код в dpr-файле проекта будет выглядеть так, как показано в листинге 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].
Интервал:
Закладка: