Единообразная передача данных, уведомления, объекты с подключением
Часто одной программе необходимо передать данные другой. Например. встроить таблицу Excel в документ WinWord или сделать что-нибудь еще. В СОМ предложен общий подход для передачи данных.
Для поддержки единообразной передачи данных разработан интерфейс IDataObject. Если реализовать поддержку этого интерфейса в одном приложении, то его данные будут доступны любым другим программам, работающим с этим интерфейсом в качестве клиента.
Однако встречаются объекты, данные которых могут со временем меняться. Очевидно, что объект должен самостоятельно оповещать своего клиента об их изменении, посылая ему соответствующие сообщения. Интерфейс IDataObject не в полной мере решает подобные проблемы. Более общий механизм для посылки объектом событий клиенту предлагает другая технология — Connectable Objects (объекты с подключением).
Любой СОМ-объект, реализующий интерфейс IDataObject, рассматривается как объект данных (data object). Данные всех таких объектов принимаются и передаются ими по одной стандартной методике. Программа, поддерживающая интерфейс IDataObject, может быть любым приложением, а данные могут находиться в файле, потоке, памяти или поступать иным путем.
Использование стандартного интерфейса для передачи данных подразумевает применение стандартных форматов представления данных.
Для описания передаваемых данных методы интерфейса IDataObieot пользуют структуру FORMATETC (от «format, etc» — «формат и прочее»). Для указания местонахождения данных с объектами данных используется структура STGMEDIUM. В этой структуре имеется ссылка на сами данные и может присутствовать интерфейсный указатель на объект, знающий, как освобождать данные.
Интерфейс IDataObject включает следующие основные методы:
• GetData — передает данные от объекта данных его клиенту. Клиент, вызывающий этот метод, передает структуру FORMA TETC, указывая формат, в котором он желает получить данные. Объект возвращает ссылку на данные в параметре типа STGMEDIUM.
• SetData — передает объекту данных данные от его клиента.
• QueryGetData — позволяет клиенту запросить объект данных о возможности пересыпки данных какого-то конкретного типа.
• ENumFormatEtc — возвращает описание форматов данных (в виде структуры FORMA TETC), поддерживаемых данным объектом.
• DAdvise — устанавливает связь между объектом данных и объектом приемника уведомлений (advise sink objecl).
• DUnadvise — разрывает связь, установленную DAdvise.
Прежде чем вызвать какой-нибудь метод IDataObject, клиент должен получить указатель на интерфейс от объекта данных. Как обычно, для этого можно использовать метод Query Interface, но есть и другие способы. Один из них — метод drag-and-drop.
Уведомления. Клиент может периодически запрашивать объект на предмет обновления данных, однако это слишком неэффективно. Лучше, если сам объект пошлет уведомление клиенту об изменении данных
Для поддержки уведомлений об изменениях в объектах данных определен интерфейс IAdviseSink, который должен поддерживать клиент.
Объекты с подключением
Объект предоставляет услуги клиенту, позволяя ему вызывать методы своего интерфейса. Ну а если объекту нужно наладить обратную связь с клиентом. Ему может потребоваться сделать запрос или просто сообщить клиенту о каком-то событии. Помимо сообщений можно использовать и СОМ.
для решения этой проблемы в общем случае используются объекты с подключением.
Точки связи и приемники
Чтобы обратится к своему клиенту, объект должен поддерживать один или несколько исходящих интерфейсов (outgoing interface). Это фактически означает, что объект должен работать с этим интерфейсом в качестве клиента. Чтобы считаться объектом с подключением или источником (source), он должен поддерживать интерфейс IConnectionPointContainer. С его помощью клиенты могут узнать, какие исходящие интерфейсы поддерживает этот объект. Каждый из этих интерфейсов представляется внутри объекта как отдельный объект точки связи (connection рoint object). Каждая точка связи отвечает только за один тип исходящего интерфейса и поддерживает, по крайней мере, интерфейс IConnectionPoint. Для реализации обратной связи необходимо, чтобы клиент включал объект или приемник (sink), реализующий исходящий интерфейс, т.е. умеющий вызывать методы этого интерфейса.
Пользуясь методами IConnectionPointContainer, клиент может выяснить, какие исходящие интерфейсы поддерживает объект и получить указатель на точку связи любого из них. После этого клиент готов к установлению связи с объектом с подключением, передавая указатель на нужный приемник одной или нескольким точкам связи объекта. Как только это будет сделано, объект сможет общаться со своим клиентом, посылая запросы и события (и то и другое осуществляется в одинаковой манере — путем вызова методов исходящего интерфейса).
Для поддержки объектов с подключением в Delphi в блоке AxCtrls предусмотрен класс TConnectionPoint, реализующий все методы интерфейса IConnectionPoint, а также класс TConnectionPoints, реализующий методы интерфейса IConnectionPointContainer.
События объектов автоматизации
События в классах Delphi реализуются с помощью указателей на метод, и нет простых способов обеспечить многопунктовость (multicasting), когда несколько клиентов вызывают одно и тоже событие. События в СОМ реализуются с помощью интерфейсов и поддерживают многопунктовость.
Последовательность создания сервера автоматизации следующая:
• Создать новое приложение, которое будет играть роль сервера, выполнив команды:
File\New Application
• Для создания объекта автоматизации следует выполнить команды: File\New\ Automation Object Будет запущен мастер по созданию объекта автоматизации.
• Первоначально в окне Automation Object Wizard необходимо указать Class Name (Eventlntf), Instancing (Multiple Instance), Threading Model (Apartment).
• Установить флажок Generate event support code в левом нижнем углу мастера.
• После нажатия кнопки ОК откроется окно Type Library Editor, в котором будет уже объявлено два интерфейса IEventlntf и IEventlntfЕvents.
• Далее следует добавить метод в интерфейс IEventlntf, например SendText, и в интерфейс, например IEventlntffivents. При этом во втором случае следует назвать его фактически событием — OnText.
После закрытия Type Library Editor будет создан файл EventSrv Pas и EventSrv_TLB.Pas.
• В файле EventSrv.Pas следует ввести строку кода в метод:
Procedure TEventIntf.SendText(Text: WideString);
Begin
FEvents.OnText(Text); // Автоматически она не создается
End;
• Далее следует запустить сервер для его регистрации.
Создание контроллера для объекта с событием
Контроллер можно создать двумя путями:
• Из объекта автоматизации создать компонент, инсталлировать его в палитру компонентов Delphi, а затем использовать его при разработке контроллера. В Delphi 5 на странице Servers уже имеется более трех десятков компонентов подобного рода.
• Объявить в приложении-клиенте класс, поддерживающий исходящий интерфейс, реализовать его методы и использовать при разработке контроллера. Этот путь требует написания большего объема кода, но не требует инсталляции класса в качестве компонента Delphi.
Обратные вызовы
Для реализации механизма обратного вызова от сервера к приложению-клиенту можно использовать как обычные интерфейсы, гак и интерфейсы диспетчеризации Интерфейс с обратным вызовом создается на сервере а реализуется на стороне клиента.
а) Для создания сервера с обратным вызовом следует выполнить следующие действия:
• Создать новое приложение и добавить в него объект автоматизации.
Назовем интерфейс IntfCallBack (флажок Generate event support code не устанавливается).
• После открытия Type Library Editor следует добавить в него интерфейс IntfCallBackEvents и включить в него метод OnText(Texl WideString); safecall;
Кроме того, следует добавить еще несколько методов
> Function Cormect(Const CallBack: IntfCallBackEvMtl) Lntegei
> Function Disconnect(UserID: Integer): Boolean,
> Procedure SendText(Text: WideString);
Далее следует воспользоваться методиками, изложенными в литературе.
б) Для создания клиентского приложения с обратным вызовом следует реализовать интерфейс IntfCallBackEvents, например, следующим образом:
Туре TEventHandler=Class(TAutoIntfObjct, IntfCallBackEvents)
Procedure OnText(Const Text: WideString); safecall; End;