ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 

Часть 18

Глава 16. Динамический обмен данными
Windows позволяет работать одновременно нескольким приложениям, поэтому часто было бы желательным, чтобы эти приложения во время работы использовали бы данные совместно. Для обеспечения обмена данными между приложениями Windows имеет буфер вырезанного из
ображения, настраиваемый протокол динамического обмена данными (dinamic data exchange - DDE), и возможность определения новых сообщений Windows и обмена ими между приложениями.
Буфер вырезанного изображения Windows
Буфер вырезанного изображения Windows служит для размещения данных различных форматов. Каждое приложение Windows, которое помещает данные в буфер вырезанного изображения может выбрать заранее определенный формат или определить свой собственный. Приложени
е, которое запрашивает данные из буфера вырезанного изображения, может запросить у него формат, по которому в данный момент хранятся данные. Приложение может размещать данные в буфере по разным форматам одновременно, предлагая тем самым варианты выбора ф
орматов для приложения, которое обращается в буфер за данными.
Форматы буфера вырезанного изображения
Заранее определенные форматы задаются константами cf_ в вызовах функций Windows:
Таблица 16.1. Форматы буфера вырезанного изображения
----------------------------------------------------------------
Формат              Значение
----------------------------------------------------------------
cf_Text             Массив символов с пустым символом в конце.
cf_Bitmap           Формат побитового расположения Windows.
cf_Sylk             Символический формат компоновки Microsoft.
cf_Tiff             Формат файла образа тега.
cf_MetafilePict     Формат изображения метафайла Windows.
----------------------------------------------------------------
Эти форматы являются распространенными стандартами, работа которых обеспечивается многими существующими приложениями Windows. Для использования специализированного, частного формата, или установления нового стандарта, вы можете зарегистрировать свой собс
твенный формат буфера вырезанного изображения. К данным, помещенным в буфер по вашему собственному формату, могут обратиться только приложения, которым известен этот формат.
Каждое приложение, которое хочет использовать свой собственный формат, должно для регистрации этого нового формата вызвать функцию RegisterClipboardFormat. первый вызов RegisterClipboardFormat в сеансе Windows возвращает новый, уникальный идентификатор ф
ормата буфера вырезанного изображения, похожий на константу cf_. Каждому новому приложению, которое регистрирует формат с тем же самым именем будет присвоен тот же самый идентификатор формата. Следовательно, все приложения будут использовать один и тот ж
е идентификатор этого нового формата данных.
Помещение данных в буфер вырезанного изображения
Помещение данных в буфер вырезанного изображения состоит из четырех этапов, выполняемых приложением, которое хочет разместить данные:
1. Открыть буфер вырезанного изображения (OpenClipbard).
2. Очистить буфер вырезанного изображения (EmptyClipbard).
3. Поместить новые данные в буфер вырезанного изображения    (SetClipbardData).
4. Закрыть буфер вырезанного изображения (CloseClipbard).
Вызов OpenClipboard берет в качестве аргумента регулятор окна. Этим гарантируется, что только одно окно манипулирует буфером вырезанного изображения в каждый момент времени.
При помещении данных в буфер приложение должно скопировать данные в глобальную память (см. Главу 14) и передать регулятор в вызов функции Windows SetClipboardData. Поскольку регулятор передан, буфер становится владельцем данных и приложение не может боль
ше ими пользоваться. Поэтому нужно обязательно передать регулятор на копию ваших данных. Функция SetClipboardData берет в качестве аргумента идентификатор формата буфера, как cf_Text. Ваше приложение может вызывать SetClipboardData многократно, каждый ра
з добавляя данные в другой формат.
Следующий пример иллюстрирует процедуру помещения данных в буфер в формате cf_Text.
function MyWindow.CopyText(TextString: PChar): Boolean;
var
 StringGlobalHandle: Handle;
 StringGlobalPtr: PChar;
begin
 CopyText:=False;
 StringGlobalHandle:=GlobalAlloc(gmem_Movable, StrLen(TextString)
  +1);
 if StringGlobalHandle <> 0 then
 begin
  StringGlobalPtr:=GlobalLock(StringGlobalHandle);
  if StringGlobalPtr: <> nil then
  begin
   StrCopy(StringGlobalPtr, TextString);
   GlobalUnlock(StringGlobalHandle);
   if OpenClipboard(HWindow) <> 0 then
   begin
    EmptyClipboard;
    SetClipboardData(cf_Text, StringGlobalHandle);
    CloseClipboard;
    CopyText:=True;
   end
   else GlobalFree(StringGlobalHandle);
  end
  else GlobalFree(StringGlobalHandle);
 end;
end;
Обратите внимание на то, что текстовые данные приложения, переданные в CopyText как аргумент TextString, копируются в StringGlobalPtr с StrCopy. Затем хранимые в StringGlobalPtr данные передаются в SetClipboardData, поэтому приложение все еще может испол
ьзовать TextString.
Выделенная с GlobaalAlloc глобальная память не должна выделяться с флагом gmem_Discardable, поскольку буфер (который будет владеть глобальным регулятором) не будет знать, как восстановить данные.
Поиск данных из буфера вырезанного изображения
Поиск данных в буфере вырезанного изображения состоит из четырех этапов, выполняемых приложением, которое хочет найти эти данные:
1. Открыть буфер вырезанного изображения (OpenClipbard).
2. Запросить текущие форматы буфера (IsClipbardFormatAvailable).
3. Взять данные из буфера вырезанного изображения    (GetClipbardData).
4. Закрыть буфер вырезанного изображения (CloseClipbard).
Аналогично помещению данных в буфер, при поиске данных в буфере используется регулятор глобальной памяти. Буфер владеет и будет владеть своими данными, поэтому ваше приложение должно скопировать связанные с регулятором данные. Приложение не должно непоср
едственно использовать и манипулировать регулятором.
Следующая функция иллюстрирует процедуру поиска текстовых данных в буфере вырезанного изображения.
function MyWindow.PasteText(TextString: PChar; TextSize: Integer):
 Integer;
var
 StringGlobalHandle: Handle;
 StringGlobalSize: Longint;
 StringGlobalPtr: PChar;
begin
 PasteText:=-1;
 if OpenClipboard(HWindow) <> 0 then
 begin
  if IsClipboardFormatAvailable(cf_Text) <> 0 then
  begin
   StringGlobalHandle:=GetClipboardData(cf_Text);
   if StringGlobalHandle <> 0 then
   begin
    StringGlobalSize:=GlobalSize(StringGlobalHandle);
    StringGlobalPtr:=GlobalLock(StringGlobalHandle);
    if StringGlobalPtr <> nil the
    begin
     if TextSize < StringGlobalSize then
      StringGlobalSize:=TextSize;
     StrLCopy(TextString, StringGlobalPtr, StringGlobalSize);
     GlobalUnlock(StringGlobalHandle);
     PasteText:=StrLen(TextString);
    end;
   end;
  end;
  CloseClipboard;
 end;
end;
Если ваше приложение может обрабатывать несколько форматов данных, то сначала нужно запросить самый информативный формат данных. Например, если буфер вырезанного изображения поддерживает двоичный формат для передачи данных, то программа должна запросить 
его до формата данных cf_Text - при преобразовании данных текст-числа могут возникнуть ошибки округления.
Отложенное воспроизведение
Приложения, которые обеспечивают несколько форматов данных, будет работать неудовлетворительно, если им придется каждый раз воспроизводить данные в каждом из форматов при их помещении в буфер. В качестве альтернативы вы можете отложить воспроизведение да
нных до тех пор, пока их не запросит другое приложение. В этом случае работа будет производиться только тогда, когда это действительно нужно.
Для откладывания воспроизведения нужно передать в вызове SetClipboardData 0 в качестве регулятора данных для всех форматов, воспроизведение которых желательно отложить. Однако, если приложение откладывает воспроизведение, оно должно хранить последние пом
ещенные в буфер данные для их последующего воспроизведения. Когда другое приложение запрашивает данные в формате отложенного воспроизведения, Windows посылает сообщение wm_RenderFormat тому приложению, которое отложило воспроизведение. Аргумент wm_Render
Format содержит запрошенный формат данных. Приложение должно поместить данные в буфер в запрошенном формате, как это было сделано в предыдущем методе, за исключением очистки буфера.
Откладывающее приложение получит сообщение wm_DestroyClipboard при следующей попытке другого приложения на доступ к содержимому буфера. Приложение может обрабатывать данные, зарезервированные для отложенного воспроизведения. Если откладывающее приложение
 разрушено, Windows посылает ему сообщение wm_RenderAllFormats, которое позволяет ему разместить данные во всех отложенных форматах.
Обмен сообщениями между приложениями
Большинство сообщений, принимаемых окном или приложением, посылаются Windows или другим окном этого же самого приложения (см. определяемы пользователем сообщения в Главе 7). Однако, одно приложение может послать сообщение другому. Этот процесс формализов
ан протоколом динамического обмена данными (dynamic data exchange - DDE), поэтому Windows предлагает механизм общего обмена данными между приложениями. Этот механизм более удобен для посылки информационных сообщений или для передачи простых значений данн
ых. DDE более подходит для передачи сложных данных.
Например, вы разрабатываете серию приложений для рабочей группы,  включающую электронную почту, планирование и календарь. Приложение электронной почты содержит специальные программы для принятия информационных сообщений почты от сервера сети. Программа п
очты при этом должна проинформировать программы календаря и планировщика о поступлении сообщения почты. В этом случае можно определить новое сообщение, например wm_EMailArrived. Каждое приложение, которое будет посылать или принимать это новое сообщение,
 должно зарегистрировать его с помощью функции Windows RegisterWindowMessage. Для посылки сообщения от одного приложения другому используется SendMessage или PostMessage. Для посылки сообщения каждому открытому окну в качестве параметра регулятора окна п
ередается HWnd(-1). Для подтверждения получения сообщения приложение-получатель может послать назад другое оговоренное сообщение.
Однако, нельзя передавать в параметре Longint (lParam) указатель на данные, поскольку Windows может переместить ваше приложение и указатель станет некорректным. Не передавайте регулятор глобальной памяти, т.к. блок памяти может быть деаллокирован, переме
щен или высвобожден. Если посылающее или принимающее сообщение не деаллокирует регулятор памяти, то также могут возникнуть проблемы. В общем случае информацию можно передавать в параметрах Word или Longint.
Динамический обмен данными
Динамический обмен данными (DDE) это протокол, по которому два приложения соглашаются обмениваться данными между собой. Слово "соглашаются" очень важно, поскольку протокол DDE требует, чтобы оба эти приложения знали, как запрашивать данные по определяемо
й приложением тематике. DDE реализуется в виде группы сообщений Windows (начинающихся с wm_DDE_), которые посылаются от окна одного приложения к окну другого приложения и могут при этом передавать данные или запрашивать их у другого приложения.
Протокол DDE также разрешает проблемы обмена глобальными данными между двумя приложениями. Для передачи строк текста он использует так называемые глобальные атомы и использует глобальную память, выделяемую для передачи данных с флагом gmem_DDEShare. Атом
ы это глобальные строковые константы, доступные всем работающим в данный момент приложениям Windows.
Термины
Когда два приложения соглашаются обмениваться данными, между ними возникает диалог. Каждое окно может участвовать только в одном диалоге в каждый момент времени. Приложение, которому нужно организовать множественный одновременный диалог, должно организов
ать несколько окон, каждое из которых будет участвовать только в единственном диалоге. В случае необходимости эти дополнительные окна могут быть невидимыми. Окно приложения, которое инициирует диалог, называется окном клиента, а окно, которое соглашается
 на диалог, называется сервером.
Протокол DDE имеет трехступенчатую систему спецификации данных для обмена. На верхнем уровне стоит имя приложения за которым следует тематика, а уже потом имя элемента данных. Имя приложения очень важно, т.к. каждый экземпляр приложения обычно "понимает"
 конкретный набор тем приложения. Например, текстовый процессор может рассматривать имена файлов документов в качестве темы диалога. Элемент это конкретный фрагмент запрошенных данных. В текстовом процессоре элементом может быть текущий параграф или шриф
т.
Приложение и тема используются для установления диалога, а элемент это единица передаваемых данных. Форматом обмена данными может быть любой из форматов буфера вырезанного изображения (см. Таблицу 16.1).
Установление диалога
Клиент инициирует диалог с сервером, который продолжается до его прекращения клиентом или сервером. Начало и окончание диалога производится соответственно сообщениями wm_DDE_Initiate и wm_DDE_Terminate.
Клиент инициирует диалог посылкой сообщения wm_DDE_Initiate с помощью функции SendMessage, в которой в качестве параметра регулятора окна задается HWnd(-1). DDE является асинхронным протоколом, поэтому только SendMessage посылает wm_DDE_Initiate. После у
становления диалога с конкретным окном для передачи сообщений DDE используется PostMessage. PostMessage отличается от SendMessage тем, что пославшее сообщение приложение продолжает работать без получения ответа. Недостаток заключается в том, что приложен
ие-клиент при этом несет ответственность за обработку произвольной реакции.
На сообщение wwm_DDE_Initiate могут ответить сразу несколько приложений-серверов и клиент должен выбрать из ни один сервер и послать сообщение wm_DDE_Terminate остальным. Таким образом только один сервер будет взаимодействовать с клиентом.
При инициации диалога с помощью wm_DDE_Initiate приложение указывает имя нужного приложения и тему диалога. Если в качестве имени приложения задано пустое значение, может начаться диалог с любым работающим в данный момент приложением. Если в качестве тем
ы диалога задано пустое значение, то может начаться диалог по любой теме.
Для инициации диалога окно может использовать следующий метод. В  качестве аргументов задаются имя приложения и тема диалога. Если вы хотите использовать пустые значения, то в качестве этих аргументов задается nil.
procedure TClientWindow.InitiateConversation(AppName, TopicName:
 PChar);
var
 AppGlobalAtom, TopicGlobalAtom: Word;
 lParam: Longint;
begin
 if AppName <> nil then
  AppGlobalAtom:=GlobalAddAtom(AppNaame)
 else AppGlobalAtom:=0;
 if TopicName <> nil then
  TopicGlobalAtom:=GlobalAddAtom(TopicName)
 else TopicGlobalAtom:=0;
 lParam:=AppGlobalAtom or (TopicGlobalAtom sh1 16);
 SendMessage(Word(-1), wm_DDE_Initiate, HWindow, lParam);
 if AppGlobalAtom <> 0 then GlobalDeleteAtom(AppGlobalAtom);
 if TopicGlobalAtom <> 0 then GlobalDeleteAtom(TopicGlobalAtom);
end;
Обратите внимание на то, что в функции SendMessage вместо указателей Longint передаются глобальные атомы. Атомы создаются только тогда, когда приложение или строка темы не nil. Приложение должно всегда освобождать любые глобальные атомы, созданные после 
вызова SendMessage. Это делается для безопасности, поскольку вызов SendMessage не вернется до тех пор, пока все окна приложений системы не получат шанс обработать сообщение wm_DDE_Initiate. Правила создания и разрушения атомов будут рассмотрены позднее в
 соответствующих сообщениях DDE. Все окна приложений, которые могут обработать посланное приложение и тему реагируют посылкой подтверждения в форма сообщения wm_DDE_Ack.
Для реакции на сообщение wm_DDE_Initiate объект окна приложения может определить автоматический метод реакции на сообщение, который будет вызываться в ответ на wm_DDE_Initiate. Данный метод будет проверять указанную тему, и если он заинтересован в диалог
е, вызовет другой метод для посылки подтверждающего сообщения окну клиента. Если задано пустое значение темы, окно сервера может выбрать тему, на которую можно было бы провести диалог. В любом случае окно-сервер обычно создает новое окно для управления д
иалогом. (Помните о том, что на каждый диалог DDE используется одно окно.) Регулятор этого нового окна должен быть передан методу, который посылает подтверждающее сообщение.
Окно сервера с именем TestServer может реагировать на сообщение wm_DDE_Initiate следующим образом:
procedure TServerWindow.ACKTopic(ClientHWnd, ConversationHWnd: HWnd;
 TopicName:  PChar);
var
 AppGlobalAtom, TopicGlobalAtom: Word;
 lParam: Longint;
begin
 AppGlobalAtom:=GlobalAddAtom('TestServer');
 TopicGlobalAtom:=GlobalAddAtom(TopicName)
 lParam:=AppGlobalAtom or (TopicGlobalAtom sh1 16);
 if SendMessage(ClieentHWnd, wm_DDE_Ack, ConversationHWnd,
  lParam)=0
  then
 begin
  GlobalDeleteAtom(AppGlobalAtom);
  GlobalDeleteAtom(TopicGlobalAtom);
 end;
end;
Окно, которое обрабатывает диалог DDE в качестве сервера, передает в качестве параметра ConversationHWnd вместо использования переменной HWindow объекта окна TServerWindow. Это еще раз подчеркивает мысль о том, что и для каждой темы диалога должен быть с
оздан новый объект окна. Клиент имеет возможность выбора темы для продолжения диалога. Также обратите внимание на то, что снова создаются глобальные атомы, которые удаляются только в случае неудачного выполнения SendMessage. Это происходит благодаря тому
, что клиент, получив сообщение wm_DDE_Ack, несет ответственность за удаление глобальных атомов.
С этого момента диалог считается установленным. Сервер знает клиента, т.к. он получил регулятор окна клиента из сообщения wm_DDE_Initiate, и клиент знает сервер, поскольку он получил регулятор окна сервера в сообщении wm_DDE_Ack. Эти регуляторы окон зате
м используются во всех сообщениях DDE между двумя приложениями.
Прекращение диалога
И окно сервера и окно клиента могут прекратить текущий диалог посылкой сообщения wm_DDE_Terminate. Обычно это сообщение посылает клиент, но сервер также может послать его при закрытии своего приложения. После принятия сообщения wm_DDE_Terminate, принявше
е его окно должно послать его назад. После обработки сообщения wm_DDE_Terminate на все поступающие сообщения DDE не будет никакой реакции и все глобальные атомы или данные должны быть удалены. После возвращения назад сообщения wm_DDE_Terminate пославшее 
его окно может быть закрыто.
Поскольку сообщения wm_DDE_Terminate могут быть посланы в экстремальных условиях (например, фатальная ошибка при закрытии приложения), возвращаемое сообщение wm_DDE_Terminate может не быть обработано. Для защиты от этой ситуации посылающее сообщение прил
ожение должно ждать поступления сообщения wm_DDE_Terminate или ждать заданное время. Хорошим приемом программирования для всех сообщений DDE, которые используют функцию PostMessage, является установление подобной задержки по времени. До вызова PostMessag
e может быть вызвана функция IsWindow, для проверки корректности параметра регулятора окна корреспондента. Эти средства защиты не являются частью протокола DDE, но помогают избегать катастрофических сбоев клиента или сервера. Лучше быть защищенным, чем п
отом жалеть об отсутствии защиты.
Методы обмена данными
Есть пять способов связи между клиентом и сервером:
1. Отдельный элемент данных может посылаться сообщениями    wm_DDE_Request и wm_DDE_Data.
2. Клиент может быть извещен об изменении элемента данных с помощью сообщений wm_DDE_Advise и wm_DDE_Data. Затем для получения данных могут быть использованы wm_DDE_Request и wm_DDE_Data. Этот механизм часто называется горячей линией.Для завершения услов
ных изменений используется wm_DDE_UnAdvise. 
3. Комбинацией механизма 1 и 2 является непрерывный wm_DDE_Data, и   wm_DDE_Request уже не требуется.
4. Сообщение wm_DDE_Poke есть инструкция серверу сменить значение   элемента данных.
5. Команда wm_DDE_Execute передает макро-команду приложению сервера для ее интерпретации и выполнения сервером.
Запрос отдельного элемента данных
Обычно окно клиента будет запрашивать прием данных от окна сервера. Отдельный элемент данных может быть запрошен следующим кодом:
procedure TClientWindow.RequestData(DataFormaat: Word; Item: PChar;
 ServerHWnd: HWnd;
var
 ItemGlobalAtom: Word;
 lParam: Longint;
begin
 ItemGlobaalAtom:=GlobalAddAtom(Item);
 lParam:=DataFormat or (ItemGlobalAtom sh1 16);
 if PostMessage(ServerHWnd, wm_DDE_Request, HWindow, lParam)=0
then
 GlobaalDeleteAtom(ItemGlobalAtom);
end;
Параметр DataFormat это один из форматов буфера вырезанного изображения, например, cf_Text или cf_DIF. Если сервер может передать данные в запрошенном формате, он может отреагировать сообщением wm_DDE_Data
...
const
 DDE_FAckReq=       $8000;
 DDE_FDeferUpd=     $4000;
 DDE_FRelease=      $4000;
 DDE_FRequested=    $4000;
type
 TDDEData=record
  bitOptions: Word;
  cfFormat: Word;
 end;
...
procedure TServerWindow.ACKData(Item: PChar; DataFormat: Word; Data:
 Pointer: DataSize: Word);
var
 DataRecccord: TDDEData;
 lParam: Longint;
 ItemGlobalAtom: Word;
 DataGlobalHandle: Word;
 DataGlobalPtr: Pointer;
begin
 DataGlobalHandle:=GlobalAlloc(gmem_Movable or gmem_DDEShare,
  SizeOf(TDDEData)+DataSize);
 if (DataGlobalHandle <> 0) then
 begin
  DataGlobaalPtr:=GlobalLock(DataGlobalHandle)
  if (DataGlobalPtr=nil) then GlobalFree(DataGlobalHandle)
  else
  begin
   DataRecord.cfFormat:=DataFormat;
   DataRecord.bitOptions:=DDE_FRequested or DDE_FAckReq;
   Move(@DataRecord, DataGlobalPtr, SizeOf(DataRecord));
   Move(Data, DataGlobalPtr+SizeOf(DataRecord), DataSize);
   GlobalUnlock(DataGlobalHandle);
   ItemGlobalAtom:=GlobalAddAtom(Item);
   lParam:=DataGlobalHandle or (ItemGlobalAtom sh1 16);
   if PostMessage(ClientHWnd, wm_DDE_Data, HWindow, lParam)=0
    then
   begin
    GlobalFree(DataGlobalHandle);
    GlobalDeleteAtom(ItemGlobalAtom);
   end;
  end;
 end;
end;
В данном примере нужно обратить внимание на два момента. Во-первых, глобальная память выделяется как gmem_DDEShare. Это позволяет и серверу и клиенту иметь доступ к данным, на которые указывает регулятор. Во-вторых, запись блока данных помещается в глоба
льной памяти перед действительными данными. Это позволяет задать формат данных путем указания различных опций битового поля, которые подсказывают принимающему клиенту способ реакции. Опция DDE_FRequested указывает, что в ответ на сообщение wm_DDE_FReques
ted посылается сообщение wm_DDE_Data. Опция DDE_FAckReq есть инструкция принимающему клиенту на поступление сообщения wm_DDE_Data послать подтверждающее сообщение wm_DDE_Ack. Если задан формат данных cf_Text, то каждая строка в передаваемых данных будет 
отделяться парой возврат каретки/смена строки, включая последнюю строку.
Если сервер не может обработать сообщение wm_DDE_FRequested, он должен послать об этом отрицательное уведомление клиенту. Это важно сделать, если сервер не может передать элемент данных в запрошенном формате. Общее правило заключается в следующем: сначал
а запрашивается самый сложный формат, потом более простой и т.д. до получения положительного подтверждения. Отрицательное уведомление будет выглядеть следующим образом:
...
ItemGlobalAtom:=GlobalAddAtom(Item);
lParam:=ItemGlobalAtom sh1 16;
if PostMessage(ClientHWnd, wm_DDE_Ack, HWindow, lParam)=0 then
 GlobalDeleteAtom(ItemGlobalAtom);
...
Если клиент принял сообщение wm_DDE_Data, он может обработать принятые данные. Он должен проверить запись TDDEData в начале указателя данных и если bitOptions содержит DDE_FAckReq, то послать назад положительное подтверждение:
...
if (DataRecord.bitOptions or DDE_FAckReq) <> 0) then
begin
 ItemGlobalAtom:=GlobalAddAtom(Item);
 lParam:=DDE_FAckReq or (ItemGlobalAtom sh1 16);
 if PostMessage(ServerHWnd, wm_DDE_Ack, HWindow, lParam)=0 then
  GlobalDeleteAtom(ItemGlobalAtom);
end;
...
Если задана битовая опция DDE_FRelease, то клиент отвечает за высвобождение регулятора памяти.
...
GlobalUnlock(ItemGlobalHandle);
if ((DataRecord.bitOptions or DDE_FRelease) <> 0) then
 GlobalFree(ItemGlobalHandle);
end;
...
В сообщении wm_DDE_Data должен быть послан флаг DDE_FAckReq или DDE_FRelease (или оба). Глобальная память освобождается либо сервером, при получении им подтверждения, либо клиентом. Если никакой флаг не передан, то сервер не знает, когда освобождать глоб
альные данные, и клиент не освободит их. Хотя все это кажется более сложным, чем есть на самом деле, эта часть протокола DDE была разработана для достижения трех важных целей:
1. Гарантировать деаллокацию всех размещенных объектов глобальной    памяти.
2. Локализовать все объекты глобальной памяти в форме, доступной и   клиенту и серверу.
3. Предотвратить ситуацию возникновения клинча, когда одно   приложение ждет реакции от другого.
Понимание того, как эти три цели реализуются во всех сообщениях DDE проясняет много сложных моментов протокола.
Продолжение передачи данных
Клиент может установить горячую линию связи с сервером для посылки извещений об изменениях элемента данных. Эта связь устанавливается с помощью посылки сообщения wm_DDE_Advise:
procedure TClientWindow.Advise(Item: PChar; DataFormat: Word);
var
 DataRecord: TDDEData;
 lParam: Longint;
 ItemGlobalAtom: Word;
 DataGlobalHandle: Word;
 DataGlobalPtr: Pointer;
begin
 DataGlobalHandle:=GlobalAlloc(gmem_Movable or gmem_DDEShare,
  SizeOf(TDDEData));
 if (DataGlobalHandle <> 0) then
 begin
  DataGlobalPtr:=GlobalLock(DataGlobalHandle)
  if (DataGlobalPtr=nil) then GlobalFree(DataGlobalHandle)
  else
  begin
   DataRecord.cfFormat:=DataFormat;
   DataRecord.bitOptions:=DDE_FDeferUpd or DDE_FAckReq;
   Move(@DataRecord, DataGlobalPtr, SizeOf(DataRecord));
   GlobalUnlock(DataGlobalHandle);
   ItemGlobalAtom:=GlobalAddAtom(Item);
   lParam:=DataGlobalHandle or (ItemGlobalAtom sh1 16);
   if PostMessage(ClientHWnd, wm_DDE_Advise, HWindow, lParam)=0
    then
   begin
    GlobalFree(DataGlobalHandle);
    GlobalDeleteAtom(ItemGlobalAtom);
   end;
  end;
 end;
end;
Обратите внимание на то, поле bitOptions в TDDEData имеет установку DDE_FDeferUpd. Это означает, что посылаемые от клиента или сервера сообщения wm_DDE_Data будут действительно содержать данные. Посылка wm_DDEData эквивалентна процедуре ACKData, за исклю
чением того, что флаг DDE_FRequested не установлен. Если поле DDE_FDeferUpd не установлено, то сообщения wm_DDE_Data будут посылаться без данных и клиент для получения данных должен послать wm_DDE_Request. Если флаг DDE_FDeferUpd установлен, то реакция м
ожет быть следующей:
...
ItemGlobalAtom:=GlobalAddAtom(Item);
lParam:=ItemGlobalAtom sh1 16;
 if PostMessage(ServerHWnd, wm_DDE_Ack, HWindow, lParam)=0 then
  GlobalDeleteAtom(ItemGlobalAtom);
...
Если сервер может послать данные в запрошенном формате, то он должен послать положительное подтверждение wm_DDE_Ack. Если он не может обслужить запрос, он должен послать отрицательный wm_DDE_Ack. По получении сообщения wm_DDE_Ack клиент может попытаться 
установить связь с другим форматом данных буфера вырезанного изображения.
Запрос серверу сменить значение данных
Сообщение wm_DDE_Poke используется как запрос серверу сменить значение элемента данных. Его можно использовать следующим образом:
procedure TClientWindow.Poke(Item: PChar; DataFormat: Word; Data:
 Pointer; DataSize: Word);
var
 DataRecord: TDDEData;
 lParam: Longint;
 ItemGlobalAtom: Word;
 DataGlobalHandle: Word;
 DataGlobalPtr: Pointer;
begin
  DataGlobalPtr:=GlobalLock(DataGlobalHandle)
  if (DataGlobalPtr=nil) then
   GlobalFree(DataGlobalHandle)
  else begin
   DataRecord.cfFormat:=DataFormat;
   Move(@DataRecord, DataGlobalPtr, SizeOf(DataRecord));
   Move(Data, DataGlobalPtr+SizeOf(DataRecord), DataSize);
   GlobalUnlock(DataGlobalHandle);
   ItemGlobalAtom:=GlobalAddAtom(Item);
   lParam:=DataGlobalHandle or (ItemGlobalAtom sh1 16);
   if PostMessage(ServerHWnd, wm_DDE_Advise, HWindow, lParam)=0
    then
   begin
    GlobalFree(DataGlobalHandle);
    GlobalDeleteAtom(ItemGlobalAtom);
   end;
  end;
 end;
end;
Код TClientWindow.Poke аналогичен TServerWindow.ACKData. Их отличие состоит в том, что от клиента серверу посылается wm_DDE_Poke, а wm_DDE_Data всегда посылается от сервера клиенту. Сервер должен послать положительное подтверждение, если он может обработ
ать wm_DDE_Poke, и отрицательное, если не может. Обрабатывая сообщение wm_DDE_Poke сервер может получить имя элемента следующим образом:
...
var
 ItemName: array[0..128] of Char;
...
ItemGlobalAtom:=lParam shr 16;
GlobalGetAtomName(ItemGlobalAtom, @ItemName, SizeOf(ItemName));
...
Выполнение макрокоманд в сервере
Сообщение wm_DDE_Execute позволяет клиенту послать на сервер строку, которая будет выполнена, как макро-команда. Если сервер представляет собой приложение текстового процессора, то командная строка будет иметь вид макро-языка текстового процессора. После
 получения сообщения wm_DDE_Execute сервер должен, как обычно, послать положительное или отрицательное подтверждение. Приведем пример его использования:
procedure TClientWindow.Execute(Command: Pointer; CommandSize: Word);
var
 lParam: Longint;
 DataGlobalHandle: Word;
 DataGlobalPtr: Pointer;
begin
 DataGlobalHandle:=GlobalAlloc(gmem_Movable or gmem_DDEShare,
  DataSize);
 if (DataGlobalHandle <> 0) then
 begin
  DataGlobalPtr:=GlobalLock(DataGlobalHandle)
  if (DataGlobalPtr=nil) then
   GlobalFree(DataGlobalHandle);
  else begin
   Move(Data, DataGlobalPtr, DataSize);
   GlobalUnlock(DataGlobalHandle);
   lParam:=DataGlobalHandle sh1 16);
   if PostMessage(ServerHWnd, wm_DDE_Execute, HWindow, lParam)=0
    then
   begin
    GlobalFree(DataGlobalHandle);
   end;
  end;
 end;
end;
Посылаемая командная строка должна заканчиваться пустым символом и иметь следующий синтаксис (прямоугольные скобки являются частью команды, а фигурные показывают необязательные элементы):
[вызовМакро]{[вызовМакро]}
вызовМакро это:
имяМакро (параметр1 {, параметр2 ... })
Например, если макро-язык вашего текстового процессора позволяет переходить на строку с определенным номером, goto_line(lineNo), и выбирать текущий параграф, select_current_paragraph, то мы можем послать следующую строку:
"[goto_line(50)][select_current_paragraph
для выбора параграфа, содержащего 50-ю строку. Для приложений, которые поддерживают DDE как сервер и имеют развитый макро-язык, список возможных воздействий безграничен.
Системная тема
Все приложения, которые поддерживают DDE, должны обеспечивать диалог на специальную тему, "System". Системная тема может использоваться клиентами, которым неизвестно ваше приложение. Через это механизм каждый клиент может узнать, как ваше приложение реал
изует DDE. Ниже приводится список элементов данных, которые должна поддерживать системная тема. Элементы данных отделяются друг от друга табуляцией.
1. SysItems - список элементов системной темы.
2. Topics - список всех текущих поддерживаемых тем. Поскольку    некоторые приложения определяют разные темы для каждого открытого файла, этот список может постоянно меняться.
3. Formats - список поддерживаемых форматов буфера вырезанного    изображения для обмена данными.



Яндекс цитирования