Поделиться через


Использование динамического обмена данными

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

Инициирование беседы

Чтобы инициировать беседу динамического обмена данными (DDE), клиент отправляет WM_DDE_INITIATE сообщение. Обычно клиент передает это сообщение путем вызова SendMessageс –1 в качестве первого параметра. Если приложение уже имеет дескриптор окна для серверного приложения, оно может отправить сообщение непосредственно этому окну. Клиент подготавливает атомы к имени приложения и имени раздела путем вызова GlobalAddAtom. Клиент может запрашивать беседы с любым потенциальным серверным приложением и на любую потенциальную тему, предоставляя атомы NULL (подстановочные знаки) для приложения и темы.

В следующем примере показано, как клиент инициирует беседу, где указаны как приложение, так и раздел.

    static BOOL fInInitiate = FALSE; 
    char *szApplication; 
    char *szTopic; 
    atomApplication = *szApplication == 0 ? 
    NULL     : GlobalAddAtom((LPSTR) szApplication); 
    atomTopic = *szTopic == 0 ? 
    NULL     : GlobalAddAtom((LPSTR) szTopic); 
 
    fInInitiate = TRUE; 
    SendMessage((HWND) HWND_BROADCAST, // broadcasts message 
        WM_DDE_INITIATE,               // initiates conversation 
        (WPARAM) hwndClientDDE,        // handle to client DDE window 
        MAKELONG(atomApplication,      // application-name atom 
            atomTopic));               // topic-name atom 
    fInInitiate = FALSE; 
    if (atomApplication != NULL) 
        GlobalDeleteAtom(atomApplication); 
    if (atomTopic != NULL) 
        GlobalDeleteAtom(atomTopic);

Заметка

Если приложение использует атомы NULL, вам не нужно использовать функции GlobalAddAtom и GlobalDeleteAtom. В этом примере клиентское приложение создает два глобальных атома, содержащие имя сервера и имя раздела соответственно.

 

Клиентское приложение отправляет сообщение WM_DDE_INITIATE с этими двумя атомами в параметре lParam сообщения. При вызове функции SendMessage специальный дескриптор окна –1 заставляет систему отправить это сообщение всем остальным активным приложениям. SendMessage не возвращается в клиентское приложение до тех пор, пока все приложения, получающие сообщение, в свою очередь, не вернули управление системе. Это означает, что все WM_DDE_ACK сообщения, отправленные в ответ серверными приложениями, гарантированно были обработаны клиентом к моменту возврата вызова SendMessage.

После возвращения SendMessage клиентское приложение удаляет глобальные атомы.

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

логика ответа на серверное приложение

Чтобы подтвердить одну или несколько тем, сервер должен создать атомы для каждой беседы (необходимы повторяющиеся атомы имен приложений, если есть несколько тем) и отправить сообщение WM_DDE_ACK для каждой беседы, как показано в следующем примере.

if ((atomApplication = GlobalAddAtom("Server")) != 0) 
{ 
    if ((atomTopic = GlobalAddAtom(szTopic)) != 0) 
    { 
        SendMessage(hwndClientDDE, 
            WM_DDE_ACK, 
            (WPARAM) hwndServerDDE, 
            MAKELONG(atomApplication, atomTopic)); 
        GlobalDeleteAtom(atomTopic); 
    } 
 
    GlobalDeleteAtom(atomApplication); 
} 
 
if ((atomApplication == 0) || (atomTopic == 0)) 
{ 
    // Handle errors. 
}

Когда сервер реагирует на сообщение WM_DDE_ACK, клиентское приложение должно сохранить дескриптор в окне сервера. Когда клиент получает дескриптор в качестве параметра wParam сообщения WM_DDE_ACK, он затем отправляет все последующие сообщения DDE в окно сервера, которое этот дескриптор идентифицирует.

Если клиентское приложение использует null atom для имени приложения или имени раздела, ожидается, что приложение получит подтверждение от нескольких серверных приложений. Несколько подтверждений также могут поступать из нескольких экземпляров сервера DDE, даже если клиентское приложение не NULL использовать атомы. Сервер всегда должен использовать уникальное окно для каждой беседы. Процедура окна в клиентском приложении может использовать дескриптор в окне сервера (предоставленный в качестве параметра lParamWM_DDE_INITIATE) для отслеживания нескольких бесед. Это позволяет одному окну клиента обрабатывать несколько бесед, не требуя завершения и повторного подключения к новому окну клиента для каждой беседы.

Передача одного элемента

После установления беседы DDE клиент может либо запросить значение элемента данных с сервера, отправив сообщение WM_DDE_REQUEST, либо отправить значение элемента данных на сервер, отправив сообщение WM_DDE_POKE.

Получение элемента с сервера

Чтобы получить элемент с сервера, клиент отправляет серверу сообщение WM_DDE_REQUEST, указывающее элемент и формат для извлечения, как показано в следующем примере.

if ((atomItem = GlobalAddAtom(szItemName)) != 0) 
{ 
    if (!PostMessage(hwndServerDDE, 
            WM_DDE_REQUEST, 
            (WPARAM) hwndClientDDE, 
            PackDDElParam(WM_DDE_REQUEST, CF_TEXT, atomItem))) 
    {
        GlobalDeleteAtom(atomItem); 
    }
} 
 
if (atomItem == 0) 
{ 
    // Handle errors. 
}

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

Получатель (сервер) сообщения WM_DDE_REQUEST обычно должен удалить атом элемента, но если вызов PostMessage завершается ошибкой, клиент должен удалить атом.

Если сервер имеет доступ к запрошенным элементам и может отобразить его в запрошенном формате, сервер копирует значение элемента в виде объекта общей памяти и отправляет клиенту сообщение WM_DDE_DATA, как показано в следующем примере.

// Allocate the size of the DDE data header, plus the data: a 
// string,<CR><LF><NULL>. The byte for the string's terminating 
// null character is counted by DDEDATA.Value[1].

size_t* pcch;
HRESULT hResult;
 
hResult = StringCchLength(szItemValue,STRSAFE_MAX_CCH, pcch);
if (FAILED(hResult))
{
// TODO: Write error handler.
 return;
}
if (!(hData = GlobalAlloc(GMEM_MOVEABLE,
  (LONG) sizeof(DDEDATA) + *pcch + 2)))  
{
    return; 
}
 
if (!(lpData = (DDEDATA FAR*) GlobalLock(hData)))  
{
    GlobalFree(hData); 
    return; 
} 
 
lpData->cfFormat = CF_TEXT;
hResult = StringCchCopy((LPSTR) lpData->Value, *pcch +1, (LPCSTR) szItemValue); // copies value to be sent
if (FAILED(hResult))
{
// TODO: Write error handler.
 return;
}
 
// Each line of CF_TEXT data is terminated by CR/LF. 
hResult = StringCchCat((LPSTR) lpData->Value, *pcch + 3, (LPCSTR) "\r\n");
if (FAILED(hResult)
{
// TODO: Write error handler.
 return;
} 
GlobalUnlock(hData); 
if ((atomItem = GlobalAddAtom((LPSTR) szItemName)) != 0) 
{ 
    lParam = PackDDElParam(WM_DDE_ACK, (UINT) hData, atomItem); 
    if (!PostMessage(hwndClientDDE, 
            WM_DDE_DATA, 
            (WPARAM) hwndServerDDE, 
            lParam)) 
    { 
        GlobalFree(hData); 
        GlobalDeleteAtom(atomItem); 
        FreeDDElParam(WM_DDE_ACK, lParam); 
    } 
} 
 
if (atomItem == 0) 
{ 
    // Handle errors.  
}

В этом примере серверное приложение выделяет объект памяти для хранения элемента данных. Объект данных инициализируется как структура DDEDATA.

Затем серверное приложение устанавливает элемент структуры cfFormat в CF_TEXT, чтобы сообщить клиентскому приложению, что данные являются текстовыми. Клиент отвечает, копируя значение запрошенных данных в член Value структуры DDEDATA. После заполнения объекта данных сервер разблокирует данные и создает глобальный атом, содержащий имя элемента данных.

Наконец, сервер выдает сообщение WM_DDE_DATA путем вызова PostMessage. Дескриптор объекта данных и атом, содержащий имя элемента, упаковываются в параметр lParam сообщения функцией PackDDElParam.

Если PostMessage завершается ошибкой, сервер должен использовать функцию FreeDDElParam, чтобы освободить упакованный параметр lParam. Сервер должен также освободить упакованный параметр lParam для полученного сообщения WM_DDE_REQUEST.

Если сервер не может удовлетворить запрос, он отправляет клиенту отрицательное WM_DDE_ACK сообщение, как показано в следующем примере.

// Negative acknowledgment. 
 
PostMessage(hwndClientDDE, 
    WM_DDE_ACK, 
    (WPARAM) hwndServerDDE, 
    PackDDElParam(WM_DDE_ACK, 0, atomItem));

После получения сообщения WM_DDE_DATA клиент обрабатывает значение элемента данных соответствующим образом. Затем, если элемент fAckReq, указывающий на сообщение WM_DDE_DATA, равен 1, клиент должен отправить серверу положительное сообщение WM_DDE_ACK, как показано в следующем примере.

UnpackDDElParam(WM_DDE_DATA, lParam, (PUINT) &hData, 
    (PUINT) &atomItem); 
if (!(lpDDEData = (DDEDATA FAR*) GlobalLock(hData)) 
        || (lpDDEData->cfFormat != CF_TEXT)) 
{ 
    PostMessage(hwndServerDDE, 
        WM_DDE_ACK, 
        (WPARAM) hwndClientDDE, 
        PackDDElParam(WM_DDE_ACK, 0, atomItem)); // Negative ACK. 
} 
 
// Copy data from lpDDEData here. 
 
if (lpDDEData->fAckReq) 
{ 
    PostMessage(hwndServerDDE, 
        WM_DDE_ACK, 
        (WPARAM) hwndClientDDE, 
        PackDDElParam(WM_DDE_ACK, 0x8000, 
            atomItem)); // Positive ACK 
} 
 
bRelease = lpDDEData->fRelease; 
GlobalUnlock(hData); 
if (bRelease) 
    GlobalFree(hData);

В этом примере клиент проверяет формат данных. Если формат не CF_TEXT (или если клиент не может заблокировать память для данных), клиент отправляет отрицательное сообщение WM_DDE_ACK, чтобы указать, что он не может обработать данные. Если клиент не может заблокировать дескриптор данных, так как дескриптор содержит элемент fAckReq, клиент не должен отправлять отрицательное сообщение WM_DDE_ACK. Вместо этого клиент должен завершить беседу.

Если клиент отправляет отрицательное подтверждение в ответ на сообщение WM_DDE_DATA, сервер отвечает за освобождение памяти (но не параметр lParam), на которое ссылается сообщение WM_DDE_DATA, связанное с отрицательным подтверждением.

Если данные могут быть обработаны, клиент проверяет элемент fAckReq структуры DDEDATA, чтобы определить, запросил ли сервер уведомление о том, что клиент успешно получил и обработал данные. Если сервер запросил эту информацию, клиент отправляет серверу положительное WM_DDE_ACK сообщение.

Поскольку разблокировка данных делает указатель на данные недействительным, клиент сохраняет значение участника fRelease перед разблокировкой объекта данных. После сохранения значения клиент проверяет, запрашивает ли серверное приложение освободить память, содержащую данные; Клиент действует соответствующим образом.

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

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

Отправка элемента на сервер

Клиент может отправить значение элемента серверу с помощью сообщения WM_DDE_POKE. Клиент отрисовывает элемент для отправки и отправляет сообщение WM_DDE_POKE, как показано в следующем примере.

size_t* pcch;
HRESULT hResult;
 
hResult = StringCchLength(szValue,STRSAFE_MAX_CCH, pcch);
if (FAILED(hResult))
{
// TODO: Write error handler.
 return;
}
if (!(hPokeData = GlobalAlloc(GMEM_MOVEABLE,
  (LONG) sizeof(DDEPOKE) + *pcch + 2))) 
{
    return; 
}
 
if (!(lpPokeData = (DDEPOKE *) GlobalLock(hPokeData))) 
{ 
    GlobalFree(hPokeData); 
    return; 
} 
 
lpPokeData->fRelease = TRUE; 
lpPokeData->cfFormat = CF_TEXT;
hResult = StringCchCopy((LPSTR) lpPokeData->Value, *pcch +1, (LPCSTR) szValue); // copies value to be sent
if (FAILED(hResult))
{
// TODO: Write error handler.
 return;
}  
 
// Each line of CF_TEXT data is terminated by CR/LF. 
hResult = StringCchCat((LPSTR) lpPokeData->Value, *pcch + 3, (LPCSTR) "\r\n");
if (FAILED(hResult)
{
// TODO: Write error handler.
 return;
}
GlobalUnlock(hPokeData); 
if ((atomItem = GlobalAddAtom((LPSTR) szItem)) != 0) 
{ 
 
        if (!PostMessage(hwndServerDDE, 
                WM_DDE_POKE, 
                (WPARAM) hwndClientDDE, 
                PackDDElParam(WM_DDE_POKE, (UINT) hPokeData, 
                    atomItem))) 
        { 
            GlobalDeleteAtom(atomItem); 
            GlobalFree(hPokeData); 
        } 
} 
 
if (atomItem == 0) 
{ 
    // Handle errors. 
} 

Заметка

Отправка данных с помощью сообщения WM_DDE_POKE по сути совпадает с отправкой с помощью WM_DDE_DATA, за исключением того, что WM_DDE_POKE отправляется с клиента на сервер.

 

Если сервер может принять значение элемента данных в формате, отображаемом клиентом, сервер обрабатывает значение элемента соответствующим образом и отправляет клиенту положительное WM_DDE_ACK сообщение. Если не удается обработать значение элемента из-за его формата или по другим причинам, сервер отправляет клиенту отрицательное WM_DDE_ACK сообщение.

UnpackDDElParam(WM_DDE_POKE, lParam, (PUINT) &hPokeData, 
    (PUINT) &atomItem); 
GlobalGetAtomName(atomItem, szItemName, ITEM_NAME_MAX_SIZE); 
if (!(lpPokeData = (DDEPOKE *) GlobalLock(hPokeData)) 
        || lpPokeData->cfFormat != CF_TEXT 
        || !IsItemSupportedByServer(szItemName)) 
{ 
    PostMessage(hwndClientDDE, 
        WM_DDE_ACK, 
        (WPARAM) hwndServerDDE, 
        PackDDElParam(WM_DDE_ACK, 0, atomItem)); // negative ACK  
}
hResult = StringCchLength(szItemValue,STRSAFE_MAX_CCH, pcch);
if (FAILED(hResult))
{
// TODO: Write error handler.
 return;
} 
hResult = StringCchCopy(szItemValue, *pcch +1, lpPokeData->Value); // copies value 
if (FAILED(hResult))
{
// TODO: Write error handler.
 return;
}  
bRelease = lpPokeData->fRelease; 
GlobalUnlock(hPokeData); 
if (bRelease) 
{ 
    GlobalFree(hPokeData); 
} 
 
PostMessage(hwndClientDDE, 
    WM_DDE_ACK, 
    (WPARAM) hwndServerDDE, 
    PackDDElParam(WM_DDE_ACK, 
         0x8000, atomItem));    // positive ACK.

В этом примере сервер вызывает GlobalGetAtomName, чтобы получить имя отправленного клиентом элемента. Затем сервер определяет, поддерживает ли он элемент и отображается ли элемент в правильном формате (то есть CF_TEXT). Если элемент не поддерживается и не отображается в правильном формате, или если сервер не может заблокировать память для данных, сервер отправляет отрицательное подтверждение обратно клиентскому приложению. Обратите внимание, что в этом случае отправка отрицательного отклика правильна, поскольку всегда предполагается, что сообщения WM_DDE_POKE имеют установленный член fAckReq. Сервер должен игнорировать участника.

Если сервер отправляет отрицательное подтверждение в ответ на сообщение WM_DDE_POKE, клиент отвечает за освобождение памяти (но не параметр lParam), на которое ссылается сообщение WM_DDE_POKE, связанное с отрицательным подтверждением.

Клиентское приложение может использовать DDE для установления ссылки на элемент в серверном приложении. После установки такой ссылки сервер отправляет периодические обновления связанного элемента клиенту, как правило, при изменении значения элемента. Таким образом, между двумя приложениями устанавливается постоянный поток данных; этот поток данных остается на месте, пока он не будет явно отключен.

Клиент инициирует ссылку на данные, публикуя сообщение WM_DDE_ADVISE, как показано в следующем примере.

if (!(hOptions = GlobalAlloc(GMEM_MOVEABLE, 
        sizeof(DDEADVISE)))) 
    return; 
if (!(lpOptions = (DDEADVISE FAR*) GlobalLock(hOptions))) 
{ 
    GlobalFree(hOptions); 
    return; 
} 
 
lpOptions->cfFormat = CF_TEXT; 
lpOptions->fAckReq = TRUE; 
lpOptions->fDeferUpd = FALSE; 
GlobalUnlock(hOptions); 
if ((atomItem = GlobalAddAtom(szItemName)) != 0) 
{ 
    if (!(PostMessage(hwndServerDDE, 
            WM_DDE_ADVISE, 
            (WPARAM) hwndClientDDE, 
            PackDDElParam(WM_DDE_ADVISE, (UINT) hOptions, 
                atomItem)))) 
    { 
        GlobalDeleteAtom(atomItem); 
        GlobalFree(hOptions); 
        FreeDDElParam(WM_DDE_ADVISE, lParam); 
    } 
} 
 
if (atomItem == 0) 
{ 
    // Handle errors 
 
}

В этом примере клиентское приложение задает флаг fDeferUpd сообщения WM_DDE_ADVISE в значение FALSE. Это указывает серверному приложению отправлять данные клиенту при изменении данных.

Если сервер не может обслуживать запрос WM_DDE_ADVISE, он отправляет клиенту отрицательное WM_DDE_ACK сообщение. Но если сервер имеет доступ к элементу и может отобразить его в запрошенном формате, сервер отмечает новую ссылку (учитывая флаги, указанные в параметре hOptions) и отправляет клиенту положительное WM_DDE_ACK сообщение. После этого, пока клиент не выдает соответствующее сообщение WM_DDE_UNADVISE, сервер отправляет новые данные клиенту каждый раз, когда значение элемента изменяется на сервере.

Сообщение WM_DDE_ADVISE устанавливает формат данных для обмена данными во время ссылки. Если клиент пытается установить другую ссылку с тем же элементом, но использует другой формат данных, сервер может отказаться от второго формата данных или попытаться поддержать его. Если для любого элемента данных установлен теплый канал, сервер может поддерживать только один формат данных одновременно. Это связано с тем, что сообщение WM_DDE_DATA для тёплого соединения имеет дескриптор данных NULL, который обычно содержит информацию о формате. Таким образом, сервер должен отклонять все теплые ссылки для элемента, уже связанного, и должен отклонять все ссылки для элемента с теплыми ссылками. Другая интерпретация может быть, что сервер изменяет формат и горячее или теплое состояние ссылки при запросе второй ссылки для того же элемента данных.

Как правило, клиентские приложения не должны пытаться установить несколько ссылок одновременно для элемента данных.

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

Серверное приложение поддерживает формат буфера обмена ссылок, помещая в буфер обмена строку, содержащую имена приложений, разделов и элементов, когда пользователь выбирает команду Копировать в меню "Изменить". Ниже приведен стандартный формат ссылки:

приложения **\0раздела\0элемента\0\0**

Один пустой символ разделяет имена, а два пустых символа завершают всю строку.

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

cfLink = RegisterClipboardFormat("Link");

Клиентское приложение поддерживает формат буфера обмена ссылок с помощью команды "Вставить ссылку" в меню "Изменить". Когда пользователь выбирает эту команду, клиентское приложение анализирует имена приложений, разделов и элементов из данных буфера обмена link-format. Используя эти имена, клиентское приложение инициирует беседу для приложения и раздела, если такая беседа еще не существует. Далее клиентское приложение отправляет WM_DDE_ADVISE сообщение серверному приложению, указывая имя элемента, содержащееся в данных буфера обмена формата Link.

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

void DoPasteLink(hwndClientDDE) 
HWND hwndClientDDE; 
{ 
    HANDLE hData; 
    LPSTR lpData; 
    HWND hwndServerDDE; 
    CHAR szApplication[APP_MAX_SIZE + 1]; 
    CHAR szTopic[TOPIC_MAX_SIZE + 1]; 
    CHAR szItem[ITEM_MAX_SIZE + 1]; 
    size_t * nBufLen; 
 HRESULT hResult;
 
    if (OpenClipboard(hwndClientDDE)) 
    { 
        if (!(hData = GetClipboardData(cfLink)) || 
                !(lpData = GlobalLock(hData))) 
        { 
            CloseClipboard(); 
            return; 
        } 
 
        // Parse the clipboard data.
  hResult = StringCchLength(lpData, STRSAFE_MAX_CCH, nBufLen);
 if (FAILED(hResult) || nBufLen == NULL)
 {
// TODO: Write error handler.
  return;
 }
 if (*nBufLen >= APP_MAX_SIZE)
        { 
            CloseClipboard(); 
            GlobalUnlock(hData); 
            return; 
        }
 hResult = StringCchCopy(szApplication, APP_MAX_SIZE +1, lpData);
 if (FAILED(hResult)
 {
// TODO: Write error handler.
  return;
 }
        lpData += (*nBufLen + 1); // skips over null
 hResult = StringCchLength(lpData, STRSAFE_MAX_CCH, nBufLen);
 if (FAILED(hResult) || nBufLen == NULL)
 {
 // TODO: Write error handler.
  return;
 }
 if (*nBufLen >= TOPIC_MAX_SIZE)
        { 
            CloseClipboard(); 
            GlobalUnlock(hData); 
            return; 
        }
 hResult = StringCchCopy(szTopic, TOPIC_MAX_SIZE +1, lpData);
 if (FAILED(hResult)
 {
 // TODO: Write error handler.
  return;
 }
        lpData += (nBufLen + 1); // skips over null
 hResult = StringCchLength(lpData, STRSAFE_MAX_CCH, nBufLen);
 if (FAILED(hResult) || nBufLen == NULL)
 {
 // TODO: Write error handler.
  return;
 }
 if (*nBufLen >= ITEM_MAX_SIZE)
        { 
            CloseClipboard(); 
            GlobalUnlock(hData); 
            return; 
        }

 hResult = StringCchCopy(szItem, ITEM_MAX_SIZE +1, lpData);
 if (FAILED(hResult)
 {
 // TODO: Write error handler.
  return;
 } 
        GlobalUnlock(hData); 
        CloseClipboard(); 
 
        if (hwndServerDDE = 
                FindServerGivenAppTopic(szApplication, szTopic)) 
        { 
            // App/topic conversation is already started. 
 
            if (DoesAdviseAlreadyExist(hwndServerDDE, szItem)) 
            {
                MessageBox(hwndMain, 
                    "Advisory already established", 
                    "Client", MB_ICONEXCLAMATION | MB_OK); 
            }
            else SendAdvise(hwndClientDDE, hwndServerDDE, szItem); 
        } 
        else 
        { 
            // Client must initiate a new conversation first. 
            SendInitiate(szApplication, szTopic); 
            if (hwndServerDDE = 
                    FindServerGivenAppTopic(szApplication, 
                        szTopic)) 
            {
                SendAdvise(hwndClientDDE, hwndServerDDE, szItem); 
            }
        } 
    } 
    return; 
}

В этом примере клиентское приложение открывает буфер обмена и определяет, содержит ли он данные в формате link (т. е. cfLink), который он ранее зарегистрировал. Если нет, или если он не может заблокировать данные в буфере обмена, клиент возвращается.

После получения указателя на данные буфера обмена клиентское приложение анализирует данные для извлечения приложения, темы и имени элемента.

Клиентское приложение определяет, существует ли беседа по теме между ним и серверным приложением. Если беседа существует, клиент проверяет, существует ли ссылка для элемента данных. Если такая ссылка существует, клиент отображает окно сообщения пользователю; в противном случае он вызывает собственную функцию SendAdvise, чтобы отправить WM_DDE_ADVISE сообщение серверу для элемента.

Если беседа по теме еще не существует между клиентом и сервером, клиент сначала вызывает собственную функцию SendInitiate для отправки сообщения WM_DDE_INITIATE для запроса беседы и, во-вторых, вызывает собственную функцию FindServerGivenAppTopic, чтобы установить беседу с окном, которое отвечает за серверное приложение. После начала беседы клиентское приложение вызывает SendAdvise, чтобы запросить ссылку.

Уведомление клиента об изменении данных

Когда клиент устанавливает ссылку с помощью сообщения WM_DDE_ADVISE, если fDeferUpd не задан (то есть равно нулю) в структуре DDEDATA, клиент запросил, чтобы сервер отправлял элемент данных каждый раз, когда изменяется значение элемента. В таких случаях сервер отображает новое значение элемента данных в указанном формате и отправляет клиенту сообщение WM_DDE_DATA, как показано в следующем примере.

// Allocate the size of a DDE data header, plus data (a string), 
// plus a <CR><LF><NULL> 

size_t* pcch;
HRESULT hResult;
 
hResult = StringCchLength(szItemValue,STRSAFE_MAX_CCH, pcch);
if (FAILED(hResult))
{
// TODO: Write error handler.
 return;
}
if (!(hData = GlobalAlloc(GMEM_MOVEABLE,
  sizeof(DDEDATA) + *pcch + 3))) 
{
    return; 
}
if (!(lpData = (DDEDATA FAR*) GlobalLock(hData))) 
{ 
    GlobalFree(hData); 
    return; 
} 
lpData->fAckReq = bAckRequest;       // as in original WM_DDE_ADVISE 
lpData->cfFormat = CF_TEXT;
hResult = StringCchCopy(lpData->Value, *pcch +1, szItemValue); // copies value to be sent
if (FAILED(hResult))
{
// TODO: Write error handler.
 return;
}
// add CR/LF for CF_TEXT format
hResult = StringCchCat(lpData->Value, *pcch + 3, "\r\n");
if (FAILED(hResult))
{
// TODO: Write error handler.
 return;
}
GlobalUnlock(hData); 
if ((atomItem = GlobalAddAtom(szItemName)) != 0) 
{ 
    if (!PostMessage(hwndClientDDE, 
            WM_DDE_DATA, 
            (WPARAM) hwndServerDDE, 
            PackDDElParam(WM_DDE_DATA, (UINT) hData, atomItem))) 
    { 
        GlobalFree(hData); 
        GlobalDeleteAtom(atomItem); 
        FreeDDElParam(WM_DDE_DATA, lParam); 
    } 
} 
 
if (atomItem == 0) 
{ 
    // Handle errors. 
 
}

В этом примере клиент обрабатывает значение элемента соответствующим образом. Если установлен флаг fAckReq для элемента, клиент отправляет серверу положительное WM_DDE_ACK сообщение.

Когда клиент устанавливает ссылку, с набором членов fDeferUpd (то есть равным 1), клиент запросил отправку только уведомления, а не самого данных при каждом изменении данных. В таких случаях, когда значение элемента изменяется, сервер не отображает значение, а просто отправляет клиенту сообщение WM_DDE_DATA с маркером данных NULL, как показано в следующем примере.

if (bDeferUpd)      // check whether flag was set in WM_DDE_ADVISE
{
    if ((atomItem = GlobalAddAtom(szItemName)) != 0) 
    { 
        if (!PostMessage(hwndClientDDE, 
                WM_DDE_DATA, 
                (WPARAM) hwndServerDDE, 
                PackDDElParam(WM_DDE_DATA, 0, 
                    atomItem)))                  // NULL data
        {
            GlobalDeleteAtom(atomItem); 
            FreeDDElParam(WM_DDE_DATA, lParam); 
        } 
    } 
} 
 
if (atomItem == 0) 
{ 
     // Handle errors. 
} 

При необходимости клиент может запросить последнее значение элемента данных, отправив обычное сообщение WM_DDE_REQUEST, или просто игнорировать уведомление с сервера о том, что данные изменились. В любом случае, если fAckReq равно 1, клиент, как ожидается, отправит положительное сообщение WM_DDE_ACK на сервер.

Если клиент запрашивает завершение определенной ссылки данных, клиент отправляет серверу сообщение WM_DDE_UNADVISE, как показано в следующем примере.

if ((atomItem = GlobalAddAtom(szItemName)) != 0) 
{ 
    if (!PostMessage(hwndServerDDE, 
            WM_DDE_UNADVISE, 
            (WPARAM) hwndClientDDE, 
            PackDDElParam(WM_DDE_UNADVISE, 0, atomItem))) 
    { 
        GlobalDeleteAtom(atomItem); 
        FreeDDElParam(WM_DDE_UNADVISE, lParam); 
    } 
} 
 
if (atomItem == 0) 
{ 
    // Handle errors. 
}

Сервер проверяет, имеет ли клиент ссылку на определенный элемент в этой беседе. Если ссылка существует, сервер отправляет клиенту положительное сообщение WM_DDE_ACK; серверу больше не требуется отправлять обновления об элементе. Если ссылка не существует, сервер отправляет клиенту отрицательное WM_DDE_ACK сообщение.

Сообщение WM_DDE_UNADVISE указывает формат данных. Формат со значением ноль сообщает серверу остановить все активные ссылки для указанного элемента, даже если установлены несколько таких ссылок, и каждая использует свой формат.

Чтобы завершить все ссылки для беседы, клиентское приложение отправляет серверу сообщение WM_DDE_UNADVISE с null атомом элемента. Сервер выясняет, есть ли у беседы хотя бы одна установленная в настоящее время ссылка. Если ссылка существует, сервер отправляет клиенту положительное сообщение WM_DDE_ACK; сервер теперь может не отправлять обновления в сеансе. Если ссылка не существует, сервер отправляет клиенту отрицательное WM_DDE_ACK сообщение.

Выполнение команд в серверном приложении

Приложения могут использовать сообщение WM_DDE_EXECUTE для выполнения определенной команды или ряда команд в другом приложении. Для этого клиент отправляет серверу сообщение WM_DDE_EXECUTE, содержащее дескриптор на командную строку, как показано в следующем примере.

HRESULT hResult;
  
if (!(hCommand = GlobalAlloc(GMEM_MOVEABLE, 
        sizeof(szCommandString) + 1))) 
{
    return; 
}
if (!(lpCommand = GlobalLock(hCommand))) 
{ 
    GlobalFree(hCommand); 
    return; 
} 

hResult = StringCbCopy(lpCommand, sizeof(szCommandString), szCommandString);
if (hResult != S_OK)
{
// TODO: Write error handler.
 return;
}
 
GlobalUnlock(hCommand); 
if (!PostMessage(hwndServerDDE, 
        WM_DDE_EXECUTE, 
        (WPARAM) hwndClientDDE, 
        PackDDElParam(WM_DDE_EXECUTE, 0, (UINT) hCommand))) 
{ 
    GlobalFree(hCommand); 
    FreeDDElParam(WM_DDE_EXECUTE, lParam); 
}

В этом примере сервер пытается выполнить указанную командную строку. В случае успешного выполнения сервер отправляет клиенту положительное сообщение WM_DDE_ACK; в противном случае он отправляет отрицательное WM_DDE_ACK сообщение. Это сообщение WM_DDE_ACK повторно использует дескриптор hCommand, переданный в исходном сообщении WM_DDE_EXECUTE.

Если строка выполнения команды клиента запрашивает завершение сервера, сервер должен ответить, отправив положительное WM_DDE_ACK сообщение, а затем опубликовать сообщение WM_DDE_TERMINATE перед завершением. Все остальные команды, отправленные с WM_DDE_EXECUTE сообщением, должны выполняться синхронно; То есть сервер должен отправлять сообщение WM_DDE_ACK только после успешного выполнения команды.

Завершение беседы

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

В следующем примере приложение, завершающее беседу, публикует сообщение WM_DDE_TERMINATE.

PostMessage(hwndServerDDE, WM_DDE_TERMINATE, 
    (WPARAM) hwndClientDDE, 0);

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

После того как приложение отправило WM_DDE_TERMINATE сообщение партнеру в DDE-сессии, оно не должно отвечать на сообщения от этого партнера, поскольку партнер, возможно, уничтожил окно, в которое будет отправлен ответ.

Если приложение получает сообщение DDE, отличное от WM_DDE_TERMINATE после публикации WM_DDE_TERMINATE, оно должно освободить все объекты, связанные с полученными сообщениями, кроме дескрипторов данных для сообщений WM_DDE_DATA или WM_DDE_POKE, которые не иметь набор элементов fRelease.

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

void TerminateConversations(hwndServerDDE) 
HWND hwndServerDDE; 
{ 
    HWND hwndClientDDE; 
 
    // Terminate each active conversation. 
 
    while (hwndClientDDE = GetNextLink(hwndClientDDE)) 
    { 
        SendTerminate(hwndServerDDE, hwndClientDDE); 
    } 
    return; 
} 
 
BOOL AtLeastOneLinkActive(VOID) 
{ 
    return TRUE; 
} 
 
HWND GetNextLink(hwndDummy) 
    HWND hwndDummy; 
{ 
    return (HWND) 1; 
} 
 
VOID SendTerminate(HWND hwndServerDDE, HWND hwndClientDDE) 
{ 
    return; 
}