WndProc в том, что возвращаемый ею ответ определяется приложением. Ниже приведен
список поступающих в функцию DdeCallback сообщений с указанием типов ожидаемых
ответов (табл. 8.5).
Таблица 8.5
Сообщение
XTYP_ADVSTART, XTYP_CONNECT
XTYP_ADVREQ, XTYP_REQUEST,
XTYP_WILDCONNECT
XTYP_ADVDATA, XTYP_EXECUTE,
XTYP_POKE
XTYP_ADVSTOP, XTYP_DISCONNECT,
XTYP_CONNECT_CONFIRM, XTYP_ERROR,
XTYP_REGISTER, XTYP_UNREGISTER,
XTYP_XACT_COMPLETE
Ответ
Логическое значение (TRUE, FALSE)
Дескриптор блока данных (или NULL)
Флаг: DDE_FACK, DDE_FBUSY или
DDE_FNOTPROCESSED
Нет ответа; только уведомление
Функция DdeClientTransaction дает команду DDEML отправить сообщение серве-
ру. Тип сообщения зависит от характера действий, запрашиваемых клиентом. В табл. 8.6
перечислены некоторые типы сообщений и соответствующие им транзакции, выполняе-
мые сервером.
Таблица 8.6
Сообщение
Транзакция
XTYP_REQUEST Подключение с необязательным ответом; передача одного элемен-
та данных
XTYP_ADVSTART Подключение с полуобязательным или обязательным ответом; пе-
редача данных и обновлений
XTYP_POKE
Запись (прием) данных от клиента
XTYP_EXECUTE Выполнение команды или действия
8.4.3. Механизмы обработки транзакций
Обработкатранзакцийснеобязательнымответом. Когда сервер получает со-
общение XTYP_REQUEST, он читает имена темы и элемента данных из двух строковых
параметров и проверяет запрашиваемый формат данных. Если сервер распознает эле-
мент и поддерживает данный формат, он создает объект данных, в который записывает
текущее значение элемента, и возвращает дескриптор этого объекта. Если сервер не
поддерживает данный формат или не распознает элемент, возвращается значение NULL.
Сервер, который не поддерживает транзакции данного типа, во избежание получе-
ния нежелательных сообщений XTYP_REQUEST должен установить в функции DdeIni-
tialize флаг CBF_FAIL_REQUESTS.
Значение, переданное сервером, возвращается клиенту в виде результата функции
DdeClientTransaction. Типичная схема такого взаимодействия представлена на рис. 8.1
[12]. Условно его можно разбить на три этапа.
1. Клиент посылает серверу запрос через DDEML.
2. Сервер расшифровывает запрос и формирует пакет запрашиваемых данных.
3. Клиент получает ответ и расшифровывает его.
Рис.8.1. Последовательность событий, происходящих после запроса клиентом элемента данных
Когда клиент делает запрос о возможности получить извещение об обновлении
данных, функция обратного вызова сервера принимает сообщение XTYP_ADVSTART.
Если сервер распознает имена темы и элемента данных, а также поддерживает запраши-
ваемый формат, он возвращает значение TRUE, подтверждая факт установления под-
ключения. Если одно из указанных условий не выполняется, сервер возвращает значение
FALSE, предотвращающее установление подключения.
Сообщение XTYP_ADVSTART не указывает серверу, с каким ответом, обязатель-
ным или полуобязательным, будет это подключение.
Транзакциисобязательнымответом. При установлении подключения с обяза-
тельным ответом сервер не возвращает данные немедленно. Он посылает их только при
последующих изменениях запрашиваемого элемента. В ответ на сообщение
XTYP_ADVSTART сервер часто устанавливает флаг, "напоминающий" ему о том, что
поступил запрос на обновление данных. При любых изменениях сервер проверяет со-
стояние этого флага и, если он установлен, вызывает функцию DdePostAdvise, которая
уведомляет DDEML о поступлении новых данных, интересующих клиента.
Сервер не должен помнить о том, какой из клиентов послал данный запрос, -
DDEML решает эту задачу собственными средствами. По заданным именам темы и эле-
мента, которые передаются сервером для идентификации доступных данных, DDEML
определяет, какой из клиентов "находится на связи" и каким, обязательным или полу-
обязательным, является подключение.
В случае обязательного подключения DDEML немедленно отправляет серверу со-
общение XTYP_ADVREQ, получает от него данные и передает их клиенту в виде сооб-
щения XTYP_ADVDATA. Этапы этого процесса можно представить следующим обра-
зом (см. рис. 8.2):
1. Инициатором сеанса выступает клиент, а сервер откликается на его запрос, посылая
ответ TRUE.
2. Сервер информирует DDEML о наличии новых данных. DDEML определяет, что
данное подключение является обязательным, и запрашивает данные, а затем переда-
ет их приложению-клиенту. Этот этап повторяется каждый раз при получении сер-
вером новых данных.
3. Клиент разрывает подключение, посылая сообщение XTYP_ADVSTOP, в ответ на
которое сервер возвращает NULL.
Рис.8.2. Цикл транзакции с обязательным ответом
Транзакциисполуобязательнымответом. В случае подключения с полуобяза-
тельным ответом DDEML также получает от сервера уведомление об изменении дан-
ных, но не отправляет ему сообщения с запросом данных. Вместо этого DDEML посы-
лает клиенту сообщение XTYP_ADVDATA с пустым дескриптором блока данных.
Сервер, который не поддерживает транзакций с обязательным и полуобязательным
ответом, должен установить флаг CBF_FAIL_ADVISES в функции DdeInitialize. Таким
образом он предотвратит получение нежелательных сообщений XTYP_ADVSTART и
XTYP_ADVSTOP.
Для получения нового значения элемента данных клиент должен выполнить тран-
закцию с необязательным ответом. На рис. 8.3 изображены этапы этого процесса.
1. Сервер объявляет о поступлении новых данных.
2. DDEML информирует клиента, но клиент игнорирует это уведомление. Уведомле-
ния будут посылаться клиенту всякий раз при изменении данных на сервере, однако
клиент может проигнорировать их всех.
3. Сервер вновь объявляет об изменениях в запрашиваемом элементе данных.
4. Клиент откликается, запрашивая данные (сообщение XTYP_REQUEST).
5. DDEML передает запрос серверу.
6. Сервер посылает запрашиваемые данные клиенту.
Рис.8.3. Цикл транзакции с полуобязательным ответом
Обработкапринудительныхикомандныхтранзакций. Некоторые серверы
предпочитают получать данные от клиента иным способом [12]. Клиент, который хочет
передать данные серверу, должен выполнить принудительную транзакцию - послать со-
общение XTYP_POKE. В ответ на это сообщение сервер проверяет тему и элемент дан-
ных и, при желании, читает объект данных.
Некоторые серверы также получают команды от своих клиентов, принимая их в
виде дескрипторов данных. Сервер блокирует объект, анализирует содержащуюся в нем
командную строку, а затем выполняет определенные действия. После анализа команд-
ной строки сервер должен освободить дескриптор данных, но при необходимости он
может скопировать эту строку для дальнейшего использования. Поскольку клиенты
обычно ожидают немедленного выполнения команд и требуют подтверждения, сервер,
по возможности, должен выполнить команду из функции обратного вызова до того, как
будет возвращен результат.
В ответ на принудительную или командную транзакцию сервер возвращает одно из
трех значений:
DDE_PACK - Данные получены и подтверждены.
DDE_FBUSY - Сервер занят и не может обработать данные или инструкции; повторите
попытку позже.
DDE_FNOTPROCESSED - Данные или инструкции отклонены.
Флаги, установленные в функции DdeInitialize, позволяют отфильтровывать неже-
лательные сообщения о принудительных (флаг CBF_FAIL_POKES) или командных
(флаг CBF_FAIL_EXECUTES) транзакциях для серверов, не поддерживающих эти тран-
закции [12].
8.4.4. Завершение DDE-диалога
При необходимости прекратить DDE-диалог клиент или сервер вызывают либо
функцию DdeDisconnect (завершить указанный диалог), либо функцию DdeDisconnect-
List (завершить группу диалогов). В обоих случаях конкретному приложению или всем
приложениям передается сообщение XTYP_DISCONNECT. Синтаксис вызова функции
DdeDisconpect имеет следующий вид:
DdeDisconnect(hConv);
где параметр hConv представляет собой дескриптор диалога, возвращаемый функцией
DdeConnect. Функция DdeDisconnectList вызывается так:
DdeDisconnectList(hConvList);
где параметр hConvList представляет собой дескриптор, возвращаемый функцией Dde-
ConnectList.
Когда DDEML получает сообщение XTYP_DISCONNECT, все активные в данный
момент транзакции текущего диалога (или диалогов) прекращаются. Согласно догово-
ренности, диалоги могут завершаться только приложениями-клиентами. Однако при
особых обстоятельствах подключения могут разрываться и приложениями-серверами
(например, в случае их закрытия).
Как клиенты, так и серверы должны быть готовы в любой момент получить сооб-
щение о разрыве подключения. При этом они должны закрыть соответствующие струк-
туры данных. В случае закрытия приложения или если оно больше не собирается уста-
навливать подключения к другим приложениям, программа должна отменить
инициализацию всех DDEML-функций обратного вызова.
При вызове функции DdeUninitialize информация о сервисе удаляется из DDEML-
таблиц, все активные в данный момент диалоги прекращаются и происходит рассылка
сообщения XTYP_UNREGISTER всем DDEML-функциям обратного вызова. Этапы
процесса завершения диалога и отключения от DDEML показаны на рис.8.4.
Рис.8.4. Процесс закрытия клиента и сервера
При возникновении ошибки DDE-функция возвращает значение NULL или
FALSE. Более подробная информация об ошибке может быть получена с помощью
функции DdeGetLastError:
UINT DdeGetLastError(DWORD dwInstID);
Поскольку функция DdeGetLastError требует задания идентификатора экземпляра
приложения, она не может применяться для получения информации об ошибке в случае
невыполнения функции DdeInitialize. Поэтому она возвращает собственные, расширен-
ные коды ошибок.
DMLERR_NO_ERROR - Успешная инициализация.
DMLERR_DLL_USAGE - Программа зарегистрирована для DDE-мониторинга и не
может использовать DDEML для выполнения транзакций.
DMLERR_INVALID_PARAMETER - Параметр содержит недопустимую информацию.
DMLERR_SYS_ERROR - Произошла внутренняя ошибка DDEML.
8.4.5. Синхронные и асинхронные транзакции
DDEML-клиенты могут выбирать между выполнением синхронных и асинхрон-
ных транзакций [12]. Отличие между синхронными и асинхронными операциями прояв-
ляется только на уровне программы-клиента. С точки зрения программы-сервера, тран-
закции обоих типов выглядят одинаково.
Преимущество асинхронных транзакций заключается в том, что они выполняются
быстрее и проще программируются. Асинхронные транзакции применяются сильно за-
груженными программами, которые должны обрабатывать значительный объем данных
при взаимодействии с DDEML-сервером, или программами, которые вынуждены регу-
лярно взаимодействовать с медленным сервером и хотят избежать нежелательных пе-
риодов ожидания ответа.
В случае синхронных операций приложение-клиент ожидает ответа от сервера,
инициализировав транзакцию - либо путем запроса информации, либо путем передачи
команды для выполнения определенных операций. Если сервер заставляет клиента ожи-
дать дольше установленного времени, функция DdeClientTransaction отменяет транзак-
цию и завершает свою работу.
При синхронных транзакциях необходимо принимать меры для освобождения объ-
ектов данных. DDEML передает дескриптор данных в виде результирующего значения
функции DdeClientTransaction и не имеет возможности узнать о том, когда данные будут
освобождены. Клиент становится владельцем объектов данных, полученных при син-
хронной транзакции от функции DdeClientTransaction, и, в конце концов, должен вы-
звать функцию DdeFreeDataHandle, чтобы освободить их.
В случае асинхронных операций при вызове функции DdeClientTransaction вместо
длительности паузы следует задать значение TIMEOUT_ASYNC. В результате функция
DdeClientTransaction немедленно возвратит значение TRUE, а также идентификатор
транзакции в поле dwResult.
Пока приложение-клиент продолжает выполнять другие операции, DDEML осуще-
ствляет транзакцию в фоновом режиме. Затем, по завершении транзакции, DDEML по-
сылает сообщение XTYP_XACT_COMPLETE функции обратного вызова приложения-
клиента. В этом сообщении в параметре dwData1 содержится идентификатор транзак-
ции, что позволяет клиенту определить, какой из запросов выполнен.
DDEML обеспечивает также альтернативный механизм идентификации транзакций
по их сообщениям о завершении. Клиент может зарегистрировать свое значение типа
DWORD, связанное с каждой асинхронной транзакцией. Функция DdeSetUserHandle
принимает дескриптор диалога, идентификатор асинхронной транзакции и пользова-
тельское значение типа DWORD, сохраняя эти параметры внутри собственной структу-
ры данных. При получении сообщения XTYP_XACT_COMPLETE клиент читает ло-
кальный идентификатор с помощью функции DdeQueryConvInfo.
Ожидая завершения асинхронной транзакции, функция DdeClientTransaction вхо-
дит в цикл и производит опрос сообщений каждого из окон, что позволяет клиенту про-
должать реагировать на команды пользователя, ожидая ответа на транзакцию. Однако в
течение этого периода клиент не может выполнить другую DDEML-функцию.
Кроме того, попытка вызвать функцию DdeClientTransaction завершается неудачно,
если у того же самого клиента в данный момент выполняется другая, синхронная тран-
закция.
Чтобы отменить асинхронную операцию, не дожидаясь ее завершения, следует вы-
звать функцию DdeAbandonTransaction. При этом DDEML освобождает все ресурсы,
связанные с транзакцией, и отменяет результаты, возвращенные сервером. (Аналогич-
ный процесс происходит по истечении времени, отведенного на выполнение синхронной
транзакции.)
При асинхронной транзакции DDEML предоставляет клиентской функции обрат-
ного вызова дескриптор объекта данных. Когда функция обратного вызова возвращает
результат, DDEML резонно предполагает, что данные больше не нужны, и автоматиче-
ски освобождает объект данных. Если клиент хочет сохранить данные, он должен их
скопировать с помощью функции DdeGetData.
Как объяснялось ранее, обычная последовательность инициации диалога предпола-
гает, что клиент знает заранее, какие ему нужны cервисы и/или темы, Чтобы предоста-
вить возможность клиентам просмотреть все доступные темы, DDE-серверы иногда со-
держат для каждого сервиса тему, которая называется system.
С помощью этой темы обеспечивается поддержка набора стандартных элементов,
Кроме того, тема system позволяет клиенту получить информацию о конкретном сервисе
[12].
Строки, идентифицирующие стандартные системные элементы, определяются в
виде констант в файле DDEML.H. Три из них должны обязательно поддерживаться все-
ми серверными приложениями.
SZDDESYS_ITEM_FORMATS - Список строк, разделенных символами табуляции и
указывающих форматы буфера обмена, которые поддерживаются данным сервером.
(Имя элемента задается строкой "Formats".)
SZDDESYS_ITEM_SYSITEMS - Список элементов, поддерживаемых сервером в теме
system. (Имя элемента задается строкой "SysItems".)
SZDDESYS_ITEM_TOPICS - Список тем, поддерживаемых сервером. (Имя элемента
задается строкой "Topics".)
Кроме этих трех элементов темы system, DDEML-сервер поддерживает в каждой
теме еще один стандартный элемент.
SZDDE_ITEM_ITEMLIST - Список элементов темы, отличней от system. (Имя элемен-
та задается строкой "TopicItemList".)
В ответ на запрос этих элементов сервер должен соединить все имена темы, эле-
мента или формата в одну длинную строку, используя в качестве разделителей символы
табуляции. Из этой строки сервер создает объект данных и возвращает библиотеке
DDEML дескриптор этого объекта в качестве результирующего значения в ответ на со-
общение XTYP_REQUEST. Затем клиент выделяет данные из этого объекта, разбивает
список на составные части и отображает их.
§ 8.5.Обменинформациейпотехнологиисвязыванияивнедрениеобъектов
8.5.1. Общие положения
Технология OLE (Object Linking and Embedding - связывание и внедрение объек-
тов) представляет собой совершенно иной способ совместного использования данных
различными приложениями по сравнению с буфером обмена и технологией DDE. В ши-
роком смысле, технология OLE является формой межзадачного взаимодействия [12]. В
частности, она позволяет одному приложению подключать или встраивать в себя ин-
формацию, созданную другим приложением. При этом создается так называемый со-
ставнойдокумент, который еще называют OLE-документом.
Написанное вами OLE-приложение будет работать правильно даже в том случае,
если оно взаимодействует с сервером, предоставляющим данные в формате, который не
предусмотрен стандартами Microsoft. Технология OLE освобождает операционную сис-
тему от необходимости отслеживать все форматы данных - если сервер способен обра-
батывать эти данные, любой клиент сможет получить их.
OLE представляет собой набор протоколов и функций, предложенных корпорацией
Aldus в 1988 году для упрощения создания и поддержки составных документов. Со-
ставнымдокументом называется файл, который принадлежит одному приложению
(например, текстовому редактору), но содержит данные, созданные другими приложе-
ниями (например, графическим редактором). Блоки "чужих" данных в составном доку-
менте называются объектами. Приложение, которое получает объекты данных и фор-
мирует из них составные документы, называется ОLE-клиентом, а приложение,
экспортирующее объекты для использования их другими приложениями, - OLE-
сервером. Является приложение клиентом или сервером, зависит от его роли в конкрет-
ной схеме взаимодействия. Одно приложение может одновременно выступать в роли как
клиента, так и сервера [12].
Интегрировав технологию OLE в операционную систему Windows, Microsoft сде-
лала большой шаг в сторону ориентации работы пользователя на документы, а не на
приложения. Традиционно пользователь вызывал отдельное приложение для каждого
нового документа. Работа с данными иного формата обычно предполагала выход из од-
ного приложения и запуск другого. В среде, где работа ведется с приложениями, доку-
мент имел смысл только в той программе, в которой он был создан.
С другой стороны, в среде разработки документов несколько приложений могут
"скооперироваться" и создать единый документ. Ни одно из приложений не распознает
форматы всех объектов, содержащихся в документе, но при переходе от одного объекта
к другому система автоматически вызывает соответствующие приложения. Редактиро-
вание различных объектов осуществляется отдельно в их "родных" приложениях, а ба-
зовый документ автоматически получает обновленные данные от всех "программ-
пайщиков". Пользователь получает возможность свободно комбинировать звук, видео,
изображения, числа и текст в едином интегрированном документе. Можно демонстри-
ровать картинки в текстовом редакторе или прилагать видеоклипы к записям в базе дан-
ных.
Составные документы существовали в операционной системе Windows еще до по-
явления технологии OLE, но возможности работы с ними были очень ограничены. Поль-
зователь мог создавать составной документ, копируя данные из буфера обмена и встав-
ляя их в другом приложении. При этом объект данных перемещался из программы-
сервера в программу-клиент. Но если сервер впоследствии редактировал объект, опера-
ции копирования и вставки необходимо было повторять для всехдокументов, в которые
была внедрена копия этого объекта [12].
Для формирования среды разработки документов операционная система должна
иметь возможность координировать работу приложений. В частности, система должна
знать, какая из программ может работать с определенным форматом данных. По мере
того как пользователь будет перемещаться от объекта к объекту в составном документе,
система должна распознавать эти объекты и поддерживать связи с соответствующими
приложениями. В Windows эти сложные операции реализованы с помощью библиотек
OLE-функций. Они помогают создавать программы, осуществляющие обработку объек-
тов практически любого типа путем невидимого взаимодействия с приложениями, соз-
давшими эти объекты.
Объект представляет собой набор данных из одного приложения, которые тракту-
ются как единое целое. OLE-приложения создают составные документы, комбинируя
несколько объектов в одном файле. Пользователь видит одновременно все объекты со-
ставного документа в одном окне. Для программы-клиента каждый объект представляет
собой "черный ящик", заполненный непонятными данными. А она и не должна пони-
мать эти данные, поскольку для манипуляций с объектами программа-клиент вызывает
OLE-функции.
При импортировании объекта программа-клиент должна выбрать один из двух ва-
риантов: связывание или внедрение [12]. Если документ содержит все данные объекта,
то такой объект называется внедренным. Если документ содержит только ссылку, ука-
зывающую на данные в другом документе, то такой объект называется связанным. Оба
метода дают на экране один и тот же результат, но только в связанных объектах автома-
тически отображаются все обновления.
Связывание объектов еще имеет то преимущество, что файл результирующего до-
кумента получается гораздо меньшим по объему. Преимущество же внедренных объек-
тов заключается в их независимости: документ, содержащий только внедренные объек-
ты, может легко переноситься из одной системы в другую. Поскольку внедренные
объекты содержат внутри себя все необходимые данные, новая система не обязательно
должна содержать OLE-сервер. При желании пользователь может преобразовать связан-
ные объекты во внедренные.
Как уже говорилось выше, OLE-приложения могут относиться к одному из двух
типов: клиенты и серверы. Если вы внедряете изображение, созданное программой Paint,
в документ текстового редактора WordPad, Paint выступает в роли сервера, а WordPad - в
роли клиента. Редактор WordPad не обязан понимать данные, которые содержатся в гра-
фическом файле, - для отображения этих данных он вызывает OLE-функции. Если сис-
тема не может самостоятельно отобразить эти данные, она вызывает сервер, т.е. Paint,
который отображает данные в окне программы WordPad.
Такие приложения, как Word, WordPad, Excel и т.п., являются одновременно и кли-
ентами, и серверами. Эти приложения могут как принимать OLE-объекты, предостав-
ленные другими серверами, так и выступать в роли OLE-серверов по отношению к дру-
гим приложениям-клиентам. Редактор Write в Windows З.х функционирует только как
OLE-клиент и не располагает возможностями сервера. А вот программа Paint служит
только OLE-сервером и не способна исполнять роль клиента. Она может только редак-
тировать изображения и передавать их другим приложениям.
Поскольку Paint является автономным приложением и в то же время представляет
собой OLE-сервер, эта программа считается полнымсервером. В отличие от полных
серверов, мини-серверы не могут работать как автономные приложения и не содержат
возможностей для открытия и сохранения файлов. Они могут только предоставлять свои
услуги OLE-клиентам.
Серверы любого типа могут предоставлять несколько типов услуг. Например, Qua-
ttro Pro предлагает услуги Quattro Pro Graph и Quattro Pro Notebook, a Microsoft Word -
Microsoft Word Document и Microsoft Word Picture.
Тип данных, экспортируемых сервером, обобщенно называется классомобъекта.
Например, программа Paintbrush экспортирует объекты класса PBrush. Excel поддержи-
вает классы ExcelWorksheet и ExcelChart. Серверы регистрируют свои классы в систем-
ном реестре. Каждым классом может управлять только один сервер.
Для каждого класса объектов регистрируется также набор команд. OLE-командой
или, по терминологии разработчиков, глаголом (verb) называется определенное дейст-
вие, которое сервер может выполнять над объектом. Например, часто используются та-