Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Операционная система Microsoft Windows 3.1 для программиста. Дополнительные главы

© Александр Фролов, Григорий Фролов
Том 17, М.: Диалог-МИФИ, 1994, 287 стр.

[Назад] [Содеожание] [Дальше]

3.3. Передача данных через канал DDEML

Итак, мы создали канал связи между сервером и клиентом. И сервер, и клиент получили и сохранили идентификаторы созданного канала связи. Теперь все готово для того чтобы приступить к передаче данных.

Передача и прием данных может выполняться в трех режимах: по явному запросу, через "теплый" канал, или через "горячий" канал.

В первом случае клиент посылает серверу запрос, указав нужный элемент данных. Сервер, получив такой запрос, предоставляет клиенту нужные данные. "Теплый" и "горячий" каналы связи создаются в тех случаях, когда клиент должен постоянно следить за изменениями данных, хранящихся в памяти сервера.

В "теплом" режиме при изменении данных сервер посылает клиенту соответствующее извещение. Получив такое извещение, клиент запрашивает у сервера новые данные.

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

Предметом нашего рассмотрения будет самый простой режим - по явному запросу клиента.

Процесс передачи данных заключается в посылке транзакций. Отметим, что транзакции бывают синхронные и асинхронные.

Клиент, пославший синхронную транзакцию, дожидается ее завершения в течение заданного интервала времени. Если по истечении этого интервала времени транзакция не завершилась, клиент получает код ошибки.

После посылки асинхронной транзакции клиент не ждет завершения транзакции. Когда транзакция будет завершена, клиент получит от системы DDEML транзакцию XTYP_XACT_COMPLETE.

В наших примерах мы будем работать с синхронными транзакциями.

Запрос данных от сервера

Для того чтобы получить данные от сервера, клиент должен послать серверу транзакцию XTYP_REQUEST. Задача посылки серверу транзакции решается с помощью функции DdeClientTransaction:

HDDEDATA WINAPI DdeClientTransaction(
  void FAR* pData, // адрес данных, передаваемых серверу 
  DWORD cbData,    // размер передаваемых данных
  HCONV hConv,     // идентификатор канала
  HSZ hszItem,     // идентификатор элемента данных
  UINT uFmt,       // формат данных
  UINT uType,      // код транзакции
  DWORD dwTimeout, // продолжительность периода ожидания
  DWORD FAR* pdwResult); // указатель на двойное слово,
     // в которое будет записан результат выполнения транзакции

Если приложение запрашивает данные у сервера, для первых двух параметров следует указать нулевые значения (так как клиент не передает данные серверу, а наоборот, запрашивает их).

Через параметр hConv следует передать идентификатор созданного ранее канала связи.

Так как по одному каналу связи можно передавать различные элементы данных, следует указать нужный элемент данных с помощью параметра hszItem.

Формат данных передается через параметр uFmt. Здесь вы можете использовать один из идентификаторов формата Clipboard, такой как CF_TEXT или CF_BITMAP, в зависимости от того, что собой представляют передаваемые данные.

Через параметр uType следует передать код транзакции, посылаемой серверу. Для запроса данных следует послать транзакцию XTYP_REQUEST.

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

Параметр pdwResult должен содержать указатель на двойное слово. В это слово будет записан код результата выполнения транзакции. Если проверка не используется, через этот параметр можно передать нулевое значение.

Приведем фрагмент кода приложения DDEMLCL, в котором выполняется запрос данных от сервера:

hData = DdeClientTransaction(NULL, 0, hConv,
   hszItem, CF_TEXT, XTYP_REQUEST, 1000, &dwResult);
if(hData != NULL)
{
  DdeGetData(hData, szBuf, nBufSize, 0L);
  return TRUE;
}

В случае успешного завершения транзакции XTYP_REQUEST функция DdeClientTransaction возвращает идентификатор области глобальной памяти, в которой расположены полученные данные.

Приложение должно переписать эти данные в свой буфер, вызвав для этого функцию DdeGetData:

DWORD WINAPI DdeGetData(
  HDDEDATA hData, // идентификатор области памяти
  void FAR* pDst, // адрес буфера
  DWORD cbMax,    // размер буфера
  DWORD cbOff);   // смещение начала данных

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

Теперь посмотрим на сервер. Получив от клиента транзакцию XTYP_REQUEST, он должен передать данные клиенту.

Приведем назначение параметров функции обратного вызова (сторона сервера) для транзакции XTYP_REQUEST:

Параметр Значение
hsz1 Идентификатор строки, содержащей имя раздела
hsz2 Идентификатор строки, содержащей имя сервиса

Возможный вариант обработчика транзакции XTYP_REQUEST показан в следующем фрагменте кода, взятом из нашего приложения DDEMLSR:

case XTYP_REQUEST:
{
  // Создаем идентификатор данных
  hData = DdeCreateDataHandle(idInst,
    szDDEServerVersion, lstrlen(szDDEServerVersion) + 1,
    0L, hszItem, CF_TEXT, 0);

  // В случае успеха возвращаем созданный идентификатор
  if(hData != NULL)
    return(hData);
  else
    return(NULL);
}

Этот фрагмент передает клиенту текстовую строку szDDEServerVersion.

Однако вначале сервер должен заказать блок глобальной памяти, записав туда передаваемые данные. Это необходимо сделать при помощи функции DdeCreateDataHandle:

HDDEDATA WINAPI DdeCreateDataHandle(
  DWORD idInst,   // идентификатор приложения
  void FAR* pSrc, // адрес буфера
  DWORD cb,       // размер буфера
  DWORD cbOff,    // смещение начала данных
  HSZ hszItem,    // идентификатор элемента данных
  UINT wFmt,      // формат данных
  UINT afCmd);    // флаги

Если с помощью функции DdeCreateDataHandle вы заказываете блок памяти, идентификатор которого будет возвращен функцией обратного вызова, последний параметр следует указать как NULL. В этом случае система DDEML освободит блок памяти самостоятельно, как только в нем отпадет потребность.

Итак, при обработке транзакции XTYP_REQUEST функция обратного вызова сервера создала блок глобальной памяти и записала туда передаваемые данные. Затем она должна возвратить идентификатор созданного блока памяти или NULL, если блок памяти создать невозможно.

Передача данных серверу

Рассмотрим обратную процедуру - передачу данных от клиента серверу.

Процедура передачи данных состоит из двух шагов. Вначале надо создать блок глобальной памяти и записать в него передаваемые данные. Для этого следует воспользоваться только что рассмотренной нами функцией DdeCreateDataHandle:

hData = DdeCreateDataHandle (idInst, szString,
   lstrlen(szString) + 1, 0L, hszItem, CF_TEXT, 0);

Затем клиент должен передать серверу транзакцию XTYP_POKE, вызвав для этого функцию DdeClientTransaction:

if(hData != NULL)
  hData = DdeClientTransaction((LPBYTE)hData, -1, hConv,
    hszItem, CF_TEXT, XTYP_POKE, 1000, &dwResult);

В качестве первого параметра функции DdeClientTransaction передается идентификатор созданного блока памяти. Обратите внимание, что размер блока задан во втором параметре функции как -1. Так и должно быть, если через первый параметр передается не указатель на область памяти, а идентификатор блока памяти.

Приведем описание параметров для транзакции XTYP_POKE:

Параметр Значение
hsz1 Идентификатор строки, содержащей имя раздела
hsz2 Идентификатор строки, содержащей имя сервиса
hData Идентификатор данных, передаваемых серверу

Обработчик транзакции XTYP_POKE в приложении DDEMLSR выглядит следующим образом:

case XTYP_POKE:
{
  // Проверяем элемент данных
  if(hsz1 == hszTopic)
  {
    // Получаем данные
    DdeGetData(hData, (LPBYTE) szDDEData, 200L, 0L);

    // Отображаем принятые данные на экране
    if(szDDEData != NULL)
    {
      MessageBox(NULL, szDDEData,
        "DDEML Server",
        MB_OK | MB_SYSTEMMODAL | MB_ICONINFORMATION);

      // Признак успешного завершения транзакции
      return((HDDEDATA)DDE_FACK);
    }
  }
  else
    return((HDDEDATA)NULL);
  break;
}

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

Обратите внимание, что в качестве признака успешного завершения транзакции возвращается значение DDE_FACK, определенное в файле ddeml.h.

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

Помимо передачи данных возможно такое взаимодействие между клиентом и сервером, когда клиент передает серверу команды в виде текстовой строки, а сервер их исполняет. Вообще говоря, этот механизм можно реализовать с помощью рассмотренных нами ранее транзакций XTYP_POKE и XTYP_REQUEST, однако существует специально предназначенная для передачи команд транзакция XTYP_EXECUTE. Вот соответствующие параметры, передаваемые функции обратного вызова:

Параметр Значение
hsz1 Идентификатор строки, содержащей имя раздела
hsz2 Идентификатор строки, содержащей имя сервиса

Процесс передачи команды очень напоминает процесс передачи данных серверу через транзакцию XTYP_POKE.

Вначале необходимо при помощи функции DdeCreateDataHandle создать блок памяти, содержащей текстовую строку команды. Отличие заключается в том, что параметр hszItem должен быть указан как NULL:

hData = DdeCreateDataHandle (idInst, szCmdString,
   lstrlen(szCmdString) + 1, 0L, NULL, wFmt, 0);

Затем с помощью функции DdeClientTransaction серверу посылается транзакция XTYP_EXECUTE:

if(hData != NULL)
  hData = DdeClientTransaction((LPBYTE)hData, -1, hConv,
    hszItem, wFmt, XTYP_EXECUTE, 1000, &dwResult);

Для того чтобы получить доступ к командной строке, обработчик транзакции XTYP_EXECUTE, расположенный в функции обратного вызова сервера, должен использовать функцию DdeAccessData:

BYTE FAR* WINAPI DdeAccessData(
  HDDEDATA hData,          // идентификатор блока памяти
  DWORD FAR* pcbDataSize); // указатель на переменную,
       // в которую будет записан размер блока памяти

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

После успешного выполнения команды функция обратного вызова сервера должна вернуть значение DDE_FACK. Если команда не поддерживается, нужно вернуть значение DDE_FNOTPROCESSED. В том случае, когда сервер может выполнить команду позже (потому что занят, например, выполнением другой команды), функция обратного вызова должна вернуть значение DDE_FBUSY.

Клиент может проверить результат выполнения передачи команды, если проанализирует содержимое двойного слова, на которое указывал параметр lpdwResult перед вызовом функции DdeClientTransaction. Например, если в этом слове установлен бит DDE_FBUSY, можно попробовать повторить посылку команды позже.

[Назад] [Содеожание] [Дальше]