Учебное пособие: Обмен данными в Windows
Имя сервиса и тема DDE–разговора используютсяпри установлении связи. В результате этого в системе должно быть установлен обмен данными между одной или несколькими парами окон, которые поддерживают данные сервис и тему. Одно из окон в каждой паре будет являться клиентом, а другое — сервером. В процессе дальнейшего DDE–разговора может происходить обмен данными, имя которых задается отдельно и является возможным именем данных для данного сервиса и темы.
Эти три имени представлены в виде атомов (ATOM), поэтому нам надо разобраться с атомами и правилами их применения перед продолжением разговора о DDE.
Атомы
Атомами в Windows называются нумерованные строки текста, хранимые в специальной таблице. Максимальный размер строки 255 символов. При помещении строки в таблицу ей присваивается уникальный номер, который собственно и используется вместо самой строки. Часто атомом называют именно эти номера, а строки — именами атомов. Обе платформы (Windows 3.x и Win32) используют для задания атомов 16-ти разрядные числа, называемые ATOM, что позволяет в одном двойном слове передавать до двух атомов.
Если добавлять две одинаковых строки в таблицу атомов, то они получат одинаковый номер. На этом основан один из способов сравнения строк — добавить обе строки в таблицу и сравнить атомы (не забудьте после этого удалить добавленный атом, даже если это было надо только для проверки).
Для каждого приложения доступны две таблицы атомов - локальная и глобальная. Так как DDE поддерживается между разными приложениями то, очевидно, должна применяться только глобальная таблица атомов.
Для работы с глобальными атомами предназначены следующие функции:
ATOM GlobalAddAtom( lpszName ); UINT GlobalGetAtomName( aAtom, lpsBuffer, nMaxCount ); ATOM GlobalFindAtom( lpszName ); ATOM GlobalDeleteAtom( aAtom );
С помощью этих функций можно добавить атомы, узнать имя конкретного атома, найти нужный атом в таблице и удалить атом. Причем, сколько раз добавлялся атом с одним именем, столько раз он должен быть удален, что бы он действительно был удален из таблицы.
Для работы с локальными атомами используются аналогичные функции, но не имеющие в начале слова “Global”. Помимо этого для работы с локальной таблицей атомов добавлена еще одна функция:
ATOM AddAtom( lpszName ); UINT GetAtomName( aAtom, lpsBuffer, nMaxCount ); ATOM FindAtom( lpszName ); ATOM DeleteAtom( aAtom );
BOOL InitAtomTable( UINT cTableEntries );
Которая позволяет указать максимальное число строк, помещаемых в локальную таблицу атомов. По умолчанию это число равно 37 (размер глобальной таблицы атомов изменить нельзя). При работе с локальной таблицей атомов удалять атомы в конце работы приложения необязательно — при завершении работы всего приложения таблица будет автоматически удалена.
При обмене данными посредством DDE происходит последовательная посылка сообщений от одного приложения к другому и наоборот. Одна DDE транзакция обычно состоит из двух — трех последовательных сообщений. В этих сообщениях часто передаются необходимые имена, представленные в виде глобальных атомов. При этом возникает необходимость так организовать обмен сообщениями, что бы обеспечить удаление всех вновь добавляемых атомов. Для этого принято такое решение: приложение, посылающее атомы вместе с сообщение должно эти атомы создать, а затем проверить код завершения функции PostMessage — если посылка прошла успешно, то атом должен быть освобожден принимающим приложением, а если при посылке возникла ошибка, то атом должно удалить посылющее приложение. В некоторых случаях атом, полученный приложением с сообщением, может быть послан вместе с ответным сообщением назад (кроме сообщения WM_DDE_INITIATE, о чем см. ниже). В этом случае не обязательно атом удалять а потом создавать снова, можно использовать полученный; однако необходимо убедиться, что он был послан с ответным сообщением и, если посылка не состоялась, его надо удалить.
Особенности задания параметра lParam сообщений DDE
Следует учесть некоторые различия в применении параметра lParam сообщений DDE на 16–ти и 32–х разрядных платформах (Windows 3.x и Win32). Дело в том, что первоначально этот параметр использовался для передачи двух значений, часто атома имени данных и хендла глобального блока. В случае 32–х разрядной платформы хендл блока сам занимает двойное слово и разместиться в lParam совместно с атомом имени данных не может.
Для решения этой проблемы были введены специальные функции, осуществляющие “упаковку” двух указанных значений (называемых младшим и старшим) в одно двойное слово. Фактически можно считать, что выделяется специальная структура, содержащая пару полей, идентификатор которой используется в качестве lParam.
Так как разные сообщения DDE используют lParam различным образом, то и упаковка данных осуществляется не для каждого сообщения, так что для каждого сообщения надо проверять необходимость использования этого механизма, а для сообщения WM_DDE_ACK надо еще проверить, в ответ на какое сообщение он послан. Для работы с “упакованными” данными необходимо применять специальные функции:
LONGPackDDElParam( UINTuMsg, UINTuLow, UINTuHigh ); LONGUnpackDDElParam( UINTuMsg, LONGlParam, PUINTpuLow, PUINTpuHigh ); LONGFreeDDElParam( UINTuMsg, LONGlParam ); LONGReuseDDElParam( LONGlParam, UINTuMsgIn, UINTuMsgOut, UINTuLow, UINTuHigh );
Функция PackDDElParam упаковывает указанные данные в промежуточную структуру, хендл которой возвращается в виде LONG; возвращаемое значение может быть использовано только для передачи данных с помощью функции PostMessage и только для некоторых сообщений DDE. Функция UnpackDDElParam выполняет обратную операцию, но промежуточная структура данных при этом сохраняется. Для того, что бы ее удалить необходимо использовать FreeDDElParam. Последняя функция ReuseDDElParam может применяться для использования одной структуры при повторной передаче данных или ответе на принятое сообщение. В качестве параметра lParam функции PostMessage необходимо указывать возвращаемое, а не первоначальное значение.
В описании параметров сообщений мы сохраняем прежний синтаксис, причем lParam может по–прежнему задаваться в виде двух компонент старший & младший. Единственное отличие, что под старшим и младшим компонентом не надо подразумевать слова, а для получения этих компонентом может потребоваться функция UnpackDDElParam, о чем в описании сообщения будет сделана специальная оговорка. В редких случаях могут даваться два описания параметров, для Windows 3.x и для Win32. Для различия этих вариантов в описании параметров сообщения могут быть добавлены специальные пояснения
(Packed Win32) — если параметр упаковывается в случае платформы Win32
(Windows 3.x) — если описание применяется только в случае Windows 3.x
(Win32) — если описание применяется только в случае Win32
DDE, начало обмена и его завершение
Когда мы говорили об общем виде протокола DDE, мы отметили, что он основан на обмене сообщениями. В большинстве случаев сообщения посылаются (post) а не передаются (send). При этом приложение, посылающее сообщение не имеет возможности дождаться обработки этого сообщения. Поэтому те данные, которые посылаются одним приложением, должны быть уничтожены другим, что бы они не оставались в памяти. Единственное исключение из этого правила — сообщения WM_DDE_INITIATE и ответ на него (WM_DDE_ACK) которые всегда передаются, а не посылаются.
При начале DDE–разговора посылается сообщение
WM_DDE_INITIATE hWnd aTopic & aService
это сообщение используется для начала DDE–разговора. Параметр wParam содержит хендл пославшего окна, а lParam в младшем слове — атом aService, а в старшем — атом aTopic, задающие, соответственно, сервис и тему DDE–разговора. Нулевые значения атомов указывают соответственно на любую поддерживаемую тему и любой сервис. Передача: Когда клиент вызывает функцию SendMessage для установления DDE–разговора, он создает необходимые атомы, а после возврата из функции SendMessage он обязан эти атомы удалить. Получение: сервер, получивший это сообщение, и поддерживающий указанные сервис и тему отвечает сообщением WM_DDE_ACK. При этом он обязан создать заново требуемые атомы — использовать атомы, полученные с сообщением запрещено. Значения атомов aTopic и aService равные 0 указывают, соответственно, на любую поддерживаемую сервером тему и сервис. В этом случае сервер должен ответить столько раз, сколько подходящих тем и сервисов он поддерживает.
Это сообщение передается всем приложениям (в качестве хендла окна–получателя сообщения используется HWND_BROADCAST или -1). Тот сервер, который поддерживает данную тему и сервис должен ответить на этот запрос передачей сообщения
WM_DDE_ACK hWnd aTopic & aService
сообщение, подтверждающее принятое прежде сообщение. Параметр wParam содержит хендл пославшего окна, а lParam используется разными способами, в зависимости от того, какое сообщение вызвало это подтверждение. В зависимости от значений параметров сообщения рассматривают положительные и отрицательные подтверждения. При ответе на WM_DDE_INITIATE младшее слово lParam содержит атом сервиса, а старшее — атом темы, при этом WM_DDE_ACK не посылается, а передается с помощью функции SendMessage, причем оно рассматривается только как положительное, так как в качестве отрицательного ответа используется отсутствие этого сообщения. Если WM_DDE_ACK получено в ответ на WM_DDE_INITIATE, то для обоих платформ — Windows 3.x и Win32 оно используется одинаково; а если оно получено в ответ на какое–либо иное сообщение, то параметр lParam будет использован для передачи “упакованного” значения. Получение: приложение–клиент, получившее сообщение WM_DDE_ACK обязано удалить все сопровождающие его атомы. Передача: при ответе на WM_DDE_INITIATE запрещено использовать полученные атомы, сервер обязан создать необходимые для ответа атомы и послать их (так как на одно WM_DDE_INITIATE может ответить несколько серверов, а при обработке WM_DDE_ACK клиент обязан удалять все атомы).