On-Line Библиотека www.XServer.ru - учебники, книги, статьи, документация, нормативная литература.
       Главная         В избранное         Контакты        Карта сайта   
    Навигация XServer.ru


http://www.avtotrans-m.ru/ Советы бывалого.




 

DDE - технология в CA-Visual Objects.

Алексей Ярцев
исполнительный директор фирмы Softservice International. Работает с Clipper c 1988 года, являлся до недавнего времени консультантом фирмы по вопросам CA-Clipper и CA-Visual Objects и преподавателем в учебном центре Softservice International

Аннотация

 

При работе в Windows достаточно часто встречаются ситуации, когда необходимо передать какие-либо данные из одного приложения в другое. Пользователь может воспользоваться для этой цели буфером обмена Windows (ClipBoard). Однако, в многозадачной среде часто требуется передавать данные из одной задачи в другую без участия пользователя. Это особенно актуально, когда возникает необходимость передавать данные, поступающие извне (например, данные от приборов или поступающие по линии связи) или данные, требующие специфичной обработки, в соответствующие приложения (например, данные из базы данных в приложение, обеспечивающее отображение деловой графики).

Microsoft Windows предоставляет механизм, позволяющий осуществить передачу данных от одного приложения другому, называемый динамическим обменом данными (Dynamic Data Exchange - DDE).

Суть DDE заключается в следующем - 'определены несколько стандартных типов сообщений (так называемые сообщения DDE), при помощи которых можно передавать в приложения дескрипторы объектов глобальной памяти, содержащие необходимые данные. Формат этих стандартных сообщений, определяющий, в каком параметре что должно находиться, и называется протоколом DDE'[3].

Другими словами, для Windows-приложения возможно создание области памяти, доступной для другого приложения.

Основные понятия DDE

Для нормального функционирования DDE необходимо, как минимум, два участника - приложение, запрашивающее данные, и приложение, посылающее данные. Приложение, запрашивающее данные, называется клиентом. Приложение, посылающее данные (обладающее данными), называется сервером.

Клиент (Client) DDE. В качестве приложения клиента может выступать любое приложение, позволяющее осуществлять DDE-запросы к DDE-серверу. В качестве примера приложения, обладающего возможностями DDE-клиента, можно привести электронную таблицу Microsoft Excel, позволяющую в качестве формулы в какой-либо ячейке устанавливать запрос с автообновлением данных к указываемому DDE-серверу.

Сервер (Server) DDE. Приложение, обеспечивающее возможности сервера, должно быть написано специальным образом, или, как минимум, обладать специальными средствами. Оно должно 'уметь' принимать DDE сообщения и посылать данные в ответ на запросы приложений-клиентов. У одного DDE-сервера может быть один или несколько DDE-клиентов.

Сеанс (Session) DDE. Сеансом называется активная непрерывная связь между клиентом и сервером.

Транзакция (Transaction) DDE. Транзакцией DDE называется одиночный акт обмена данными в процессе сеанса.

Тема (Topic) и раздел (Item). Любые данные, передаваемые в процессе сеанса DDE, принадлежат какому-либо разделу, который, в свою очередь, принадлежит какой-либо теме. Каждый сервер DDE имеет несколько тем, которые он обслуживает, а каждая тема делится на несколько разделов. При инициализации сервера DDE регистрируется одна или несколько поименованных тем и разделов данных тем. Только сообщения, принадлежащие темам и разделам данного сервера, будут обрабатываться сервером DDE.

Передаваемые данные, как правило, являются строками. Передавать строки в качестве параметров не удобно. Поскольку при передаче сообщения от одного приложения другому генерируется соотвествующее событие, обрабатываемое диспетчером событий Windows, то, естественно, событие не может 'таскать' за собой строку данных. Поэтому в DDE существует специальный способ кодирования строк. Для этого используется универсальный объект обмена данными между приложениями - атом (atom).

Microsoft Windows поддерживает в памяти таблицу атомов (atom table), которая состоит из набора элементов, каждый из которых содержит целочисленное значение, являющееся уникальным идентификатором атома, и строку, содержащую соответствующие данные. Приложения могут добавлять и удалять соответствующие атомы из таблицы атомов. При добавлении новой строки Windows присваивает ей уникальный идентификатор, формирует атом и заносит его в таблицу атомов. Кроме того, Windows позволяет не только добавлять и удалять атомы, но, также, извлекать строку по идентификатору и наоборот. Таким образом, при попытке передачи данных от одного приложения другому собственно данные заносятся в таблицу атомов, а идентификатор атома передается вместе с соответствующим событием.

Существует ряд функций SDK для работы с глобальной таблицей атомов и ряд стандартных сообщений DDE, позволяющих пересылать данные между приложениями. Я не буду приводить их в данной статье, поскольку CA-Visual Objects предоставляет собственные средства поддержки DDE, однако, Вы можете обратиться к документации к Windows SDK, или, например, к книге [3].

Способы обмена данными

Рассмотрим способы обмена данными между приложениями.

Выполнение команд

DDE позволяет приложению-клиенту послать сообщение серверу о необходимости выполнения той или иной команды. Информационная часть данного сообщения содержит не что иное, как символьное имя команды, которую приложение-сервер должно выполнить. Данное сообщение не требует никакого подстверждения со стороны сервера и, по сути, является пересылкой строки данных от клиента к серверу. Сервер должен обработать данную строку-команду и выполнить действия, соответствующие данной команде. Естественно, действия, выполняемые сервером, зависят лишь от него самого и являются, фактически, результатом обработки полученной текстовой строки. Например, клиент может послать команду 'STOP' серверу, который выполнит какие-либо действия в ответ на полученную текстовую строку (например, выполнит метод App:Quit()). Этот способ обмена данными наиболее прост.

Разовый запрос данных

В данном случае клиент может послать запрос серверу, в котором сообщается, что клиент хочет получить от сервера какие-либо данные. Поскольку в запросе указывается тема и раздел, к которым принадлежат запрашиваемые данные, то сервер может легко определить, какие именно данные требуются клиенту. Сервер должен обработать данный запрос и передать клиенту соответствующие данные, которые клиент обязан принять. Каждый запрос обрабатывается сервером один раз, т.е. на каждый запрос сервер один раз посылает запрашиваемые данные. Данный способ связи также называется 'холодным' связыванием.

Запрос данных с необходимостью обновления

В этом случае механизм обмена данными несколько сложнее. Клиент посылает запрос серверу, в котором сообщает о необходимости переслать клиенту данные, и, в дальнейшем, пересылать указанные данные в случае их обновления. В ответ на запрос сервер должен переслать запрашиваемые данные, а затем, по мере обновления данных, автоматически пересылать их запросившему клиенту. Приложение-клиент, в свою очередь, должно обеспечить прием приходящих от сервера данных и оповещать сервер каждый раз, когда данные пришли. Сервер (теоретически) не должен посылать новую порцию измененных данных, пока клиент не пришлет подтверждения о получении более ранней порции. Последнее условие не обязательно, но его игнорирование может привести к неприятным последствиям, таким, как, например, обрыв связи между клиентом и сервером. Данный способ обмена данными называется также 'горячим' связыванием.

Существует еще один способ обмена данными, называемый 'теплым' связыванием. Он представляет собой нечто среднее между 'горячим' и 'холодным' связыванием. Клиент посылает серверу запрос на получение данных и обязывает сервер извещать клиента каждый раз о изменении запрошенных данных, не пересылая сами данные. Клиент, получив оповещение об изменении данных от сервера, может послать запрос на измененные данные и получить их, а может и не посылать запроса. Таким образом, клиент контролирует поток изменяемых данных от сервера. Однако, данный способ обмена данными CA-Visual Objects не поддерживает.

Средства поддержки DDE, предоставляемые CA-Visual Objects

CA-Visual Objects предоставляет средства для написания как приложений-клиентов, так и приложений серверов. Любое приложение может быть легко превращено в DDE-сервер или в DDE-клиент, выполняя при этом любые дополнительные функции. Поддержка DDE осуществляется через специальные классы, разделяющиеся на три категории - клиентские, серверные и общие, описывающие пересылаемые данные (группа Ipc-классов).

Классы клиента

IpcClient

Выполняет поиск и соединение с сервером и предоставляет набор методов для запроса и получения данных

IpcClientErrorEvent

Событие, содержащее информацию о произошедшем сбое при передаче клиентом запроса или команды.

Классы сервера

IpcServer

Регистрирует приложение как DDE-сервер и описывает набор тем и разделов, обрабатываемых данным сервером. Имеет набор методов, обрабатывающих запросы и команды от DDE-клиента.

IpcDataRequestEvent

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

IpcExecuteRequestEvent

Событие, содержащее информацию о команде, посылаемой серверу клиентом, включая тему, раздел, к которому принадлежит команда и саму команду в виде текстовой строки.

Общие классы

IpcTopic

Содержит информацию о теме и разделах данной темы. Объекты данного класса (темы) могут быть зарегистрированы сервером до начала сеанса связи или добавлены в течении сеанса.

IpcTopicData

Содержит непосредственно ссылку на передаваемые от сервера данные (строку) и размер (длину) этих данных. Используется при передаче данных от сервера клиенту.

IpcDataUpdateEvent

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

Теперь последовательно рассмотрим действия, необходимые для описания клиента и сервера.

Регистрация клиента

Для регистрации клиента необходимо создать объект класса IpcClient:

 

CLASS MyClient INHERIT IpcClient

 

Описание класса наследника от стандартного IpcClient необходимо, поскольку в дальнейшем прийдется переопределять ряд методов, осуществляющих автоматический прием данных от сервера. Однако, если использовать клиента только для передачи команд (прием каких-либо данных от сервера не осуществляется), то можно использовать непосредственно базовый класс IpcClient.

 

...
oClient := MyClient{'INTER'}

 

В качестве параметра передается символьное имя сервера, с которым устанавливается связь.

Регистрация сервера

Для регистрации сервера необходимо создать объект класса IpcServer и зарегистрировать набор тем и разделов, обслуживаемых данным сервером:

 

CLASS MyServer INHERIT IpcServer

...
// в качестве параметра - символьное имя сервера
oServer := MyServer{'INTER'}

// в качестве параметра - символьное имя темы
oTopic := IpcTopic{'Topic1'}

// добавляем несколько разделов
oTopic:AddItem('Item1')
oTopic:AddItem('Item2')

// регистрируем тему на сервере
oServer:AddTopic(oTopic)

Выполнение команд

Для выполнения команды клиент должен выполнить метод IpcClient:Execute(), посылающий серверу команду. У сервера в этом случае будет вызван метод IpcServer:ExecuteRequest(), которому в качестве параметра будет передано событие IpcExecuteRequestEvent, содержащее информацию о команде.

 

Клиент:

 

oClient:Execute('Topic1','Item1','Command1')

 

Сервер:

 

METHOD ExecuteRequest(oEvent) CLASS MyServer

if oEvent:Topic='Topic1'.and.oEvent:Item='Item1';
.and.oEvent:Command='Command1'
tone(100,1) // например, издать звук
end

Разовый запрос данных

Для выполнения разового запроса клиент должен выполнить метод IpcClient:RequestData(), в параметрах которого указать объект класса IpcTopic, описывающий тему, данные для которой необходимо запросить у сервера и логический параметр, указывающий на метод обновления данных (FALSE для одиночного запроса). Сервер реагирует на данное сообщение автоматическим вызовом метода IpcServer:DataRequest(), которому передается событие IpcDataRequestEvent, содержащее информацию о теме, требующей обновления. Данный метод сервера должен осуществить выбор данных, соответствующих теме, и передать их клиенту путем возврата из метода значения, содержащего объект класса IpcTopicData, который описывает передаваемые в момент текущей транзакции данные. Таким образом, вызываемый метод IpcServer:DataRequest() не только реагирует на запрос данных, но и посылает данные обратно клиенту. Клиент же реагирует на посылку данных сервером автоматическим вызовом метода IpcClient:DataUpdate(), который принимает в качестве параметра событие IpcDataUpdateEvent, содержащее тему и раздел пришедших от сервера данных и средства доступа к самим данным в виде метода IpcDataUpdateEvent:GetData().

 

Клиент:

 

// запрос данных клиентом
(oTopic:=IpcTopic{'Topic1')):AddItem('Item1')
oClient:RequestData(oTopic, TRUE)
...
// реакция на приход данных
METHOD DataUpdate(oEvent) CLASS MyClient
if oEvent:Topic='Topic1'.and.oEvent:Item='Item1'
? oEvent:GetData()
end

Сервер:

// реакция сервера на получение
// запроса на данные от клиента
METHOD DataRequest(oEvent) CLASS MyServer
local cTmp := "" as string

// проверяем соответствие темы и раздела
if oEvent:Topic="Topic1'.and.oEvent:Item="Item1
cTmp:='Обновленные данные'
else
cTmp:= NULL_STRING
end

// переносим строку в статическую память,
// поскольку данные в атомах должны храниться
// в статической памяти, чтобы сборщик мусора CA-VO
// (garbage collector) не удалил данные из памяти прежде, чем
// они будут прочитаны приложением-клиентом.
self:pStringData := stringalloc(cTmp)
// нельзя однако, забывать, что выделенную под данные память
// необходимо в последствии вернуть системе при помощи
// функции memfree(), иначе она будет потеряна

// возвращаем объект класса IPCTopicData, формируя его "на лету"
return IPCTopicData{ptr(_cast,self:pStringData), len(cTmp) }

Запрос данных с необходимостью обновления

Для выполнения запроса данных с необходимостью обновления клиент должен выполнить те же действия, что и при разовом запросе; единственное отличие - во втором параметре метода IpcClient:RequestData(), указывающем на метод обновления данных (TRUE для запроса с обновлением). Сервер реагирует на данное сообщение точно так же, как и в случае одиночного запроса, но, дополнительно, должен сам отслеживать изменения данных и вызывать обновление данных клиента путем прямого обращение к методу IpcServer:UpdateTopic(), с указанием темы и раздела обновляемых данных. Метод IpcServer:UpdateTopic() самостоятельно вызывает метод IpcServer:DataRequest(), который, по описанной выше схеме, производит пересылку данных клиенту. Таким образом, клиенту достаточно послать один запрос серверу, а затем обрабатывать поступающие измененные данные от сервера при помощи метода IpcClient:DataUpdate(). Сервер же должен самостоятельно отслеживать изменения данных и отсылать их при помощи вызова вышеописанного метода IpcServer:UpdateTopic(). Пример на данный вид связи можно найти на сопровождающей журнал дискете в файле ESERVER.AEF.

Обратная связь

Для более надежной схемы передачи (особенно, в случае запроса с необходимостью обновления) желательно осуществлять систему обратной связи между сервером и клиентом. Сразу оговорюсь, что схема обратной связи работает не всегда, поскольку зависит от реализации приложения-клиента и приложения-сервера (не везде она предусмотрена). И если вы создаете приложение-сервер для уже существующего клиента, и наоборот, то механизм обратной связи, скорее всего, не предусмотрен в приложении для которого вы пишите 'партнера'.

Суть системы обратной связи достаточно проста: клиент, при получении запрошенных данных, должен оповестить об этом сервер, который не должен посылать следующие обновленные данные, пока клиент не оповестит его о получении предыдущих. Более глубокий смысл заключается в том, что, поскольку при передаче данных распределяется статическая память Windows, которую впоследствии необходимо возвратить 'в систему', то сервер, послав данные, может осуществить попытку высвобождения памяти до того, как клиент считает необходимую информацию. На более низком уровне программирования (использование непосредственно Windows API) можно установить, кто - клиент или сервер - будет высвобождать память после прохождения транзакции DDE. CA-Visual Objects не предоставляет такого рода сервиса, поэтому, приняв систему обратной связи, можно предоставить серверу возможность высвобождать распределенную память без опасности для клиента.

Реализация системы обратной связи следующая: при получении очередных обновленных данных (метод IpcClient:DataUpdate()) клиент может сообщить серверу о приходе данных при помощи метода IpcClient:ChangeData(), который вызывает метод сервера IpcServer:DataUpdate(). В данном методе сервер может обрабатывать сообщение о получении данных клиентом и, например, устанавливать какой-либо флаг, позволяющий дальнейшую передачу обновленных данных от сервера к клиенту.

Обработка клиентом ошибок DDE

При осуществлении запроса или при передаче команды от клиента к серверу может возникать ряд ошибочных ситуаций, например, на указанном сервере не зарегистрирована запрашиваемая тема и т.д. В этом случае клиенту сообщается о произошедшей ошибке при помощи вызова метода IpcClient:ClientError(), которому передается информация об этой ошибке в виде события IpcClientErorEvent, содержащего описание ошибочной ситуации. Клиент должен обработать ошибочную ситуацию и выполнить необходимые действия. Существует четыре предусмотренных ошибочных ситуации (константы, описанные ниже, определены в библиотеке System Library:

 

IPCSERVERNOTFOUND Приложение-сервер не найдено.

IPCTOPICNOTFOUND На указанном сервере не найдена запрашиваемая тема.

IPCITEMNOTFOUND На указанном сервере в указанной теме отсутствует раздел.

IPCOUTOFMEMORY Для операции DDE не хватает памяти.

 

Обработка клиентом сообщения об ошибке может выглядеть следующим образом.

Клиент:

 

METHOD ClientError(oEvent) CLASS MyClient

if oEvent:ErrorType==IPCSERVERNOTFOUND
(errorbox{,"Приложение-сервер не найдено!"}):Show()
elseif oEvent:ErrorType==IPCTOPICNOTFOUND
(errorbox{,"Раздел на приложении-сервере не найден!"}):Show()
elseif oEvent:ErrorType==IPCITEMNOTFOUND
(errorbox{,"Тема раздела на приложении-сервере не найдена!"}):Show()
elseif oEvent:ErrorType==IPCOUTOFMEMORY
(errorbox{,"Недостаточно памяти для выполнения операции..."}):Show()
else
(errorbox{,"Произошло что-то еще..."}):Show()
end

Деинициализация DDE связи

После завершения работы с DDE необходимо удалить из памяти все ссылки, как на DDE-клиента, так и на DDE-сервер. У обоих классов (IPCServer и IPCClient) существует метод Destroy(), который высвобождает внутренние ресурсы и удаляет ссылку на клиента и сервер в глобальной таблице атомов Windows. При удалении DDE-клиента необходимо, также, выполнить метод Execute() и сообщить серверу о прерывании запроса с обновлением. В противном случае, если ранее запрос с обновлением был послан, сервер будет продолжать посылать данные, пока сам не закроется или пока какой-либо другой клиент не отменит данный запрос. Естественно, сообщать серверу о прекращении действия запроса с обновлением необходимо только в том случае, если ранее данный запрос был послан.

Примеры использования DDE-технологии

На дискете сопровождения находятся два примера, использующие DDE-технологию. Первый пример рассматривает возможности установки связи между двумя приложениями, написанными на CA-Visual Objects (DDEC.AEF - приложение клиент, DDES.AEF - приложение сервер). Данные приложения должны быть откомпилированы и скомпонованы в .EXE модули. Приложение-клиент (DDEC.EXE) автоматически запускает приложение-сервер (DDES.EXE), после чего вы можете установить и разорвать связь, а также проследить различные виды связи.

Второй пример рассматривает возможность связи между приложением-сервером на CA-Visual Objects и Microsoft Excel в качестве приложения-клиента. Исходный код приложения-сервера находится в файле ESERVER.AEF, а пример таблицы, отображающей данные от сервера, находится в файле ESERVER.XLS. Для нормального функционирования примера необходимо сначала запустить приложение-сервер (ESERVER.EXE), затем запустить Microsoft Excel (использовалась версия Microsoft Excel 5.0) и загрузить таблицу ESERVER.XLS. Пример демонстрирует возможности связывания приложений с необходимостью обновления и может быть полезен на практике.



Литература по Cavo