Главная » Delphi » События автоматизации

0

Программисты Delphi давно освоили работу с событиями, особенно в части  их ис пользования  для  создания  обычных  элементов  управления.  Перетащим  кнопку   в форму,  дважды щелкаем  на ее событии OnClick в окне  инспектора объектов, пишем нужный   программный код —   и  вся  недолга.   Даже  при  создании  новых   элементов управления с событиями особых  проблем не возникает: создаем  новый тип  метода, добавляем в данный элемент управления поле и опубликовав свойство остаемся впол не довольны собой.  Однако у разработчиков COM в среде  Delphi одно  только  упоми нание  о событиях может  вызвать нервную  дрожь.  Многие  разработчики Delphi ста раются избегать событий COM лишь потому,  что у них,  видите  ли, “нет времени раз бираться во всех этих  штучках”. Если к последней группе  относитесь и вы, читатель,то  вам  в  очередной раз  будет  предоставлена  возможность  убедиться, что  “не  так страшен черт…”, — благодаря прекрасной встроенной поддержке Delphi. И хотя  все новые   термины,  связанные с  событиями автоматизации,  могут,  казалось  бы,  еще больше  усложнить общую картину, в настоящем разделе, думаю, нам удастся добрать ся до самой сути этих событий, и вскоре  вы не сможете сдержать недоумения: “И все го делов то?!”.

Что такое события?

Попросту говоря, события обеспечивают серверу  средство организации обратного вызова  в приложении клиенте для  предоставления ему определенной информации. При  использовании традиционной модели  “клиент/сервер” клиент вызывает сервер для выполнения некоторого действия или  получения некоторых данных, сервер вы полняет необходимое действие или получает  данные, после  чего  управление возвра щается  клиенту.  В большинстве случаев эта модель прекрасно работает, но она оказы вается  бессильной, когда событие, в котором заинтересован клиент, асинхронно по своей  природе или зависит от элементов пользовательского интерфейса. Например, когда клиент посылает серверу  требование загрузить файл,  и при этом он (клиент) не хочет  бесполезно тратить время  на ожидание окончания загрузки, после чего он смог бы  продолжить свою  работу  (особенно при  использовании достаточно медленной связи  через модем).  Для клиента предпочтительней было  бы использовать такую мо дель, при  которой сразу после  отправки серверу  некоторой инструкции можно  было бы продолжать работу до тех пор,  пока сервер не уведомит  клиента о завершении за грузки  файла. Аналогично, даже  такой  простейший способ  работы с пользователь ским интерфейсом, как щелчок  на кнопке, служит хорошим примером ситуации, в ко торой серверу  потребуется некоторый  механизм уведомления клиента об  имевшем место событий. Очевидно, что сам клиент не может вызвать в сервере метод,  который будет ожидать щелчка на некоторой кнопке.

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

События в Delphi

Delphi при  подходе  к событиям придерживается методологии KISS (keep  it simple, stupid!  — “чем проще, тем лучше”). События реализуются как указатели  на методы — если назначить эти указатели  определенным методам  в приложении, то они  могут быть  вы званы  с помощью этого  указателя. В качестве иллюстрации рассмотрим сценарий раз работки приложения, в задачу которого входит  обработка события в компоненте. Если на данную ситуацию  посмотреть абстрактно, то “сервер”  в данном  случае будет компо нентом, определяющим и генерирующим событие. А роль  “клиента”  будет исполнять приложение, которое использует этот  компонент, поскольку  оно подключается к собы тию, назначая имя некоторого заданного метода указателю события.Несмотря на то что именно эта простая модель генерации событий и делает  среду разработки в Delphi  элегантной и простой в применении, ради такой  простоты опре деленно приходится жертвовать некоторыми возможностями. Например, не сущест вует встроенного способа разрешить сразу нескольким клиентам “услышать” одно и то же событие — это называется мультивещанием (multicasting). Кроме  того,  невозможно динамически получить описание типа для события, не написав некоторого кода RTTI (который, возможно, и не пришлось бы использовать в приложении из за привязки к конкретной версии).

События в автоматизации

Если о модели  событий Delphi можно  сказать, что  она  проста, но весьма  ограни ченна, то  модель  событий автоматизации отличается большими  возможностями, и большей сложностью. Опытные программисты уже догадались, что  в технологии ав томатизации события реализуются с помощью интерфейсов.  Но  события определя ются не на основе пары  метод событие, а только  как часть  интерфейса. Такой  интер фейс  часто  называется интерфейсом  событий (events  interface), или  исходящим интер фейсом (outgoing interface). Это название связано с тем,  что  он реализуется не самим сервером (как другие интерфейсы), а его клиентами, причем методы  интерфейса бу дут вызываться со стороны сервера. Как и все интерфейсы, интерфейсы событий имеют  соответствующие идентификаторы  интерфейсов  (IID),  которые определяют их уникальным образом. Кроме  того,  описание интерфейса событий (подобно иным интерфейсам) содержится в библиотеке типов  объекта автоматизации,  связанной с компонентным классом этого объекта автоматизации.

В серверах, интерфейсы событий которых необходимо сделать  доступными для клиентов, следует  реализовать интерфейс IConnectionPointContainer. Этот  ин терфейс определен в модуле ActiveX следующим образом:

type

IConnectionPointContainer = interface

[‘{B196B284-BAB4-101A-B69C-00AA00341D07}’]

function EnumConnectionPoints(

out Enum: IEnumConnectionPoints): HResult; stdcall;

function FindConnectionPoint(const iid: TIID;

out cp: IConnectionPoint): HResult; stdcall;

end;

В терминологии COM под точкой подключения (connection point) понимают неко торые средства, предоставляющие программный доступ  к исходящему  интерфейсу. Если клиенту  нужно выяснить, поддерживает ли сервер события, то для этого  доста точно вызвать функцию QueryInterface для интерфейса IConnectionPointCon- tainer. Если  такой  интерфейс присутствует, то  сервер может  служить  источником событий.  Метод   EnumConnectionPoints() интерфейса  IConnectionPointCon- tainer позволяет клиентам опросить все исходящие интерфейсы, поддерживаемые сервером. Для получения конкретного внешнего интерфейса клиенты могут исполь зовать метод FindConnectionPoint().

Нетрудно заметить, что метод FindConnectionPoint() обеспечивает клиента ин 

терфейсом IConnectionPoint, который представляет нужный исходящий интерфейс.

Интерфейс IConnectionPoint также  определен в модуле ActiveX, и это объявление

имеет следующий вид:type

IConnectionPoint = interface

[‘{B196B286-BAB4-101A-B69C-00AA00341D07}’]

function GetConnectionInterface(

out iid: TIID): HResult; stdcall;

function GetConnectionPointContainer(

out cpc: IConnectionPointContainer): HResult; stdcall;

function Advise(const unkSink: IUnknown;

out dwCookie: Longint): HResult; stdcall;

function Unadvise(dwCookie: Longint): HResult; stdcall;

function EnumConnections(

out Enum: IEnumConnections): HResult; stdcall;

end;

Метод  GetConnectionInterface() интерфейса IConnectionPoint предоставля ет идентификатор (IID)  исходящего интерфейса, поддерживаемый данной точкой под ключения.  А  метод   GetConnectionPointContainer() предоставляет  интерфейс IConnectionPointContainer (описанный ранее),  который  управляет этой  точкой подключения. Весьма  любопытен метод  Advise. Именно он  воплощает в реальность магию связывания исходящих событий на сервере с интерфейсом events, реализован ным  клиентом. Первый параметр, передаваемый данному  методу,  представляет собой клиентскую реализацию интерфейса events, второй — это cookie, идентифицирующий это конкретное подключение. Функция  Unadvise() предназначена для разрыва соеди нения, установленного ранее  между клиентом и сервером с помощью функции Ad- vise(). Функция  EnumConnections() позволяет клиенту  опросить все  активные  в данный момент подключения, т.е. все соединения, выполненные функцией Advise().

Ввиду очевидной путаницы, которая может  возникнуть, если описывать участников этих  отношений просто как клиент и сервер, в технологии автоматизации было  решено использовать несколько другие  термины, позволяющие однозначно определить, “кто есть кто”. Поэтому  реализацию исходящего интерфейса, содержащегося внутри  клиен та, договорились называть стоком (sink), а объект сервера, который генерирует собы тия для клиента, — источником (source).

Из  всего  вышесказанного, надеемся, вполне очевидно, что  события автоматиза ции имеют  два преимущества перед  событиями Delphi. А именно: они  могут быть мультивещательными,  поскольку   функцию   IConnectionPoint.Advise() в  этом случае можно  вызывать более  одного  раза. И, кроме  того,  события автоматизации яв ляются  самоописательными (благодаря использованию библиотеки типов  и методов перечисления), поэтому ими можно управлять динамически.

в Delphi

Итак, все эти рассуждения звучат прекрасно, но как на самом деле заставить собы тия  автоматизации работать в среде  Delphi?  С удовольствием ответим, а в качестве примера создадим  приложение сервер автоматизации, которое предоставляет неко торый внешний интерфейс, и приложение клиент, которое реализует сток для этого интерфейса. Учтите, что  вовсе  не  обязательно быть  экспертом по  части  точек  под ключения, стоков, источников и прочих премудростей, чтобы  добиться от Delphi же лаемых  результатов. Однако  глубокое  понимание того,  что  действительно происхо дит под покровами оболочек и инкапсулирующих функций Delphi, сослужит хорошую и, надеемся, полезную  службу.Сервер

Создание сервера начнем с создания нового приложения. В качестве примера созда дим новое  приложение, содержащее одну форму  с полем  текстового редактора (компонент TMemo), которое будет управляться клиентом — как показано на рис. 15.12.

Рис. 15.14. Клиент автоматизации, манипули

рующий сервером и обрабатывающий события

Источник: Тейксейра, Стив, Пачеко, Ксавье.   Borland Delphi 6. Руководство разработчика. : Пер.  с англ. — М. : Издательский дом “Вильямс”, 2002. —  1120 с. : ил. — Парал. тит. англ.

По теме:

  • Комментарии