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

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

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

Чтобы инициировать беседу Динамического обмена данными (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 клиентское приложение удаляет глобальные атомы.

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

server application response logic

Чтобы подтвердить одно или несколько разделов, сервер должен создавать атомы для каждой беседы (требуя повторяющихся атомов имен приложений при наличии нескольких разделов) и отправлять 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 для имени приложения или имени раздела, ожидается, что приложение получит подтверждение от нескольких серверных приложений. Несколько подтверждений также могут поступать из нескольких экземпляров сервера DDE, даже если клиентское приложение не использует атомы NULL . Сервер всегда должен использовать уникальное окно для каждой беседы. Процедура окна в клиентском приложении может использовать дескриптор окна сервера (предоставленный в качестве параметра lParam WM_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 между приложениями, просто копируя элемент данных в серверном приложении и вставляя его в клиентское приложение.

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

application**\0 topic\0item\0\0**

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

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

cfLink = RegisterClipboardFormat("Link");

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

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

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; 
}