Udostępnij za pośrednictwem


Korzystanie z dynamicznej wymiany danych

Ta sekcja zawiera przykłady kodu w następujących zadaniach:

Inicjowanie konwersacji

Aby zainicjować konwersację dynamicznej wymiany danych (DDE), klient wysyła komunikat WM_DDE_INITIATE. Zazwyczaj klient emituje ten komunikat przez wywołanie SendMessage, z –1 jako pierwszy parametr. Jeśli aplikacja ma już dojście do aplikacji serwera, może wysłać komunikat bezpośrednio do tego okna. Klient przygotowuje atomy dla nazwy aplikacji i nazwy tematu, wywołując GlobalAddAtom. Klient może żądać rozmów z dowolną potencjalną aplikacją serwera i dla dowolnego potencjalnego tematu, podając NULL jako atomy (symbol wieloznaczny) dla aplikacji i tematu.

W poniższym przykładzie pokazano, jak klient inicjuje konwersację, w której określono zarówno aplikację, jak i temat.

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

Notatka

Jeśli aplikacja używa atomów null, nie musisz używaćGlobalAddAtom i funkcji globalDeleteAtom. W tym przykładzie aplikacja kliencka tworzy dwa globalne atomy zawierające odpowiednio nazwę serwera i nazwę tematu.

 

Aplikacja kliencka wysyła komunikat WM_DDE_INITIATE z tymi dwoma atomami w parametrze lParam komunikatu. W wywołaniu funkcji SendMessage specjalny uchwyt okna –1 kieruje system do wysyłania tego komunikatu do wszystkich innych aktywnych aplikacji. SendMessage nie wraca do aplikacji klienckiej, dopóki wszystkie aplikacje, które odbierają komunikat, nie zwrócą kontroli do systemu. Oznacza to, że wszystkie komunikaty WM_DDE_ACK wysyłane w odpowiedzi przez aplikacje serwera mają gwarancję, że zostały przetworzone przez klienta do momentu, gdy wywołanie SendMessage zwróci wynik.

Po zakończeniu działania SendMessage aplikacja kliencka usuwa globalne atomy.

Aplikacje serwera reagują zgodnie z logiką pokazaną na poniższym diagramie.

Aby uznać co najmniej jeden temat, serwer musi utworzyć atomy dla każdej konwersacji (wymagane są atomy z nazwą aplikacji, jeśli istnieje wiele tematów) i wysłać wiadomość WM_DDE_ACK dla każdej konwersacji, jak pokazano w poniższym przykładzie.

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

Gdy serwer odpowie komunikatem WM_DDE_ACK, aplikacja kliencka powinna zapisać uchwyt do okna serwera. Klient otrzymujący uchwyt jako parametr wParam komunikatu WM_DDE_ACK następnie wysyła wszystkie kolejne komunikaty DDE do okna serwera, które identyfikuje ten uchwyt.

Jeśli aplikacja kliencka używa atomu NULL dla nazwy aplikacji lub nazwy tematu, można oczekiwać, że aplikacja otrzyma potwierdzenia z więcej niż jednej aplikacji serwerowej. Wiele potwierdzeń może również pochodzić z wielu wystąpień serwera DDE, nawet jeśli aplikacja kliencka nie NULL używa atomów. Serwer powinien zawsze używać unikatowego okna dla każdej konwersacji. Procedura okna w aplikacji klienckiej może używać dojścia do okna serwera (podanego jako lParam parametru WM_DDE_INITIATE) w celu śledzenia wielu konwersacji. Dzięki temu pojedyncze okno klienta może przetwarzać kilka konwersacji bez konieczności kończenia i ponownego nawiązywania połączenia z nowym oknem klienta dla każdej konwersacji.

Przenoszenie pojedynczego elementu

Po ustanowieniu konwersacji DDE klient może pobrać wartość elementu danych z serwera, wydając komunikat WM_DDE_REQUEST lub przesyłać wartość elementu danych do serwera, wydając WM_DDE_POKE.

Pobieranie elementu z serwera

Aby pobrać element z serwera, klient wysyła serwer WM_DDE_REQUEST komunikat określający element i format do pobrania, jak pokazano w poniższym przykładzie.

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

W tym przykładzie klient określa format schowka CF_TEXT jako preferowany format żądanego elementu danych.

Odbiornik (serwer) komunikatu WM_DDE_REQUEST zazwyczaj musi usunąć atom elementu, ale jeśli wywołanie PostMessage zakończy się niepowodzeniem, klient musi usunąć atom.

Jeśli serwer ma dostęp do żądanego elementu i może go renderować w żądanym formacie, serwer kopiuje wartość elementu jako obiekt pamięci udostępnionej i wysyła klientowi komunikat WM_DDE_DATA, jak pokazano w poniższym przykładzie.

// 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.  
}

W tym przykładzie aplikacja serwera przydziela obiekt pamięci do przechowywania elementu danych. Obiekt danych jest inicjowany jako struktura DDEDATA.

Następnie aplikacja serwera ustawia składową cfFormat struktury na CF_TEXT, informując aplikację kliencką, że dane są w formacie tekstowym. Klient odpowiada, kopiując wartość żądanych danych do członka Wartość struktury DDEDATA. Po wypełnieniu obiektu danych serwer odblokuje dane i tworzy globalny atom zawierający nazwę elementu danych.

Na koniec serwer wysyła komunikat WM_DDE_DATA, wywołując PostMessage. Dojście do obiektu danych i atom zawierający nazwę elementu są pakowane do parametru lParam komunikatu przez funkcję PackDDElParam.

Jeśli PostMessage zakończy się niepowodzeniem, serwer musi użyć funkcji FreeDDElParam, aby zwolnić spakowany parametr lParam. Serwer musi również zwolnić spakowany parametr lParam dla komunikatu WM_DDE_REQUEST, który otrzymał.

Jeśli serwer nie może spełnić żądania, wysyła do klienta komunikat ujemny WM_DDE_ACK, jak pokazano w poniższym przykładzie.

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

Po otrzymaniu komunikatu WM_DDE_DATA klient przetwarza odpowiednio wartość elementu danych. Następnie, jeśli element fAckReq wskazywany w komunikacie WM_DDE_DATA wynosi 1, klient musi wysłać serwerowi pozytywny komunikat WM_DDE_ACK, jak pokazano w poniższym przykładzie.

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

W tym przykładzie klient sprawdza format danych. Jeśli format nie jest CF_TEXT (lub jeśli klient nie może zablokować pamięci dla danych), klient wysyła negatywny komunikat WM_DDE_ACK, aby wskazać, że nie może przetworzyć danych. Jeśli klient nie może zablokować dojścia danych, ponieważ dojście zawiera członka fAckReq, klient nie powinien wysyłać komunikatu odmowy WM_DDE_ACK. Zamiast tego klient powinien zakończyć konwersację.

Jeśli klient wysyła negatywne potwierdzenie w odpowiedzi na komunikat WM_DDE_DATA, serwer jest odpowiedzialny za zwolnienie pamięci (ale nie parametru lParam) powiązanej z komunikatem WM_DDE_DATA skojarzonym z negatywnym potwierdzeniem.

Jeśli może przetworzyć dane, klient zbada fAckReq elementu struktury DDEDATA, aby ustalić, czy serwer zażądał informacji o tym, że klient odebrał i przetworzył dane pomyślnie. Jeśli serwer zażądał tych informacji, klient wysyła serwerowi komunikat dodatni WM_DDE_ACK.

Ponieważ odblokowywanie danych unieważnia wskaźnik do tych danych, klient zapisuje wartość członka fRelease przed odblokowaniem obiektu danych. Po zapisaniu wartości klient zbada go, aby określić, czy aplikacja serwera zażądała od klienta zwolnienia pamięci zawierającej dane; klient działa odpowiednio.

Po otrzymaniu negatywnego komunikatu WM_DDE_ACK klient może ponownie zażądać tej samej wartości elementu, podając inny format schowka. Zazwyczaj klient najpierw poprosi o najbardziej złożony format, który obsługuje, a następnie w razie potrzeby przechodzi przez progresywnie prostsze formaty, dopóki nie znajdzie takiego, który serwer może dostarczyć.

Jeśli serwer obsługuje element Formaty tematu systemowego, klient może jednorazowo określić, jakie formaty schowka obsługuje serwer, zamiast określać je za każdym razem, gdy klient żąda elementu.

Przesyłanie elementu do serwera

Klient może wysłać wartość elementu do serwera przy użyciu komunikatu WM_DDE_POKE. Klient renderuje element do wysłania i wysyła komunikat WM_DDE_POKE, jak pokazano w poniższym przykładzie.

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

Notatka

Wysyłanie danych przy użyciu komunikatu WM_DDE_POKE jest zasadniczo takie samo jak wysyłanie ich przy użyciu WM_DDE_DATA, z wyjątkiem tego, że WM_DDE_POKE jest wysyłany z klienta do serwera.

 

Jeśli serwer może zaakceptować wartość elementu danych w formacie renderowany przez klienta, serwer przetwarza wartość elementu zgodnie z potrzebami i wysyła klientowi komunikat dodatni WM_DDE_ACK. Jeśli nie może przetworzyć wartości elementu ze względu na jego format lub z innych powodów, serwer wysyła klientowi negatywny komunikat 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.

W tym przykładzie serwer wywołuje GlobalGetAtomName w celu pobrania nazwy elementu wysłanego przez klienta. Następnie serwer określa, czy obsługuje element i czy element jest renderowany w poprawnym formacie (czyli CF_TEXT). Jeśli element nie jest obsługiwany i nie jest renderowany w poprawnym formacie lub jeśli serwer nie może zablokować pamięci dla danych, serwer wysyła negatywne potwierdzenie z powrotem do aplikacji klienckiej. Należy pamiętać, że w tym przypadku wysłanie negatywnego potwierdzenia jest poprawne, ponieważ przyjmuje się, że komunikaty WM_DDE_POKE zawsze mają ustawiony element fAckReq. Serwer powinien ignorować członka.

Jeśli serwer wysyła negatywne uznanie w odpowiedzi na komunikat WM_DDE_POKE, klient jest odpowiedzialny za zwolnienie pamięci (ale nie parametru lParam) odwołuje się do wiadomości WM_DDE_POKE skojarzonej z negatywnym uznaniem.

Aplikacja kliencka może używać funkcji DDE do ustanowienia linku do elementu w aplikacji serwera. Po ustanowieniu takiego linku serwer wysyła okresowe aktualizacje połączonego elementu do klienta, zwykle za każdym razem, gdy wartość elementu ulegnie zmianie. W związku z tym między dwiema aplikacjami ustanawiany jest stały strumień danych; ten strumień danych pozostaje w miejscu, dopóki nie zostanie jawnie odłączony.

Klient inicjuje łącze danych, publikując komunikat WM_DDE_ADVISE, jak pokazano w poniższym przykładzie.

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 
 
}

W tym przykładzie aplikacja kliencka ustawia flagę fDeferUpd komunikatu WM_DDE_ADVISE na FALSE. Spowoduje to przekierowanie aplikacji serwera do wysyłania danych do klienta za każdym razem, gdy dane się zmienią.

Jeśli serwer nie może obsługiwać żądania WM_DDE_ADVISE, wysyła klientowi komunikat ujemny WM_DDE_ACK. Jeśli jednak serwer ma dostęp do elementu i może go renderować w żądanym formacie, serwer zauważa nowy link (przypominając flagi określone w parametrze hOptions) i wysyła klientowi komunikat dodatni WM_DDE_ACK. Od tego czasu do momentu, aż klient wyświetli pasujący komunikat WM_DDE_UNADVISE, serwer wysyła nowe dane do klienta za każdym razem, gdy wartość elementu zmienia się na serwerze.

Komunikat WM_DDE_ADVISE określa format danych, które mają być wymieniane podczas łączenia. Jeśli klient próbuje ustanowić kolejne połączenie z tym samym elementem, ale używa innego formatu danych, serwer może odrzucić drugi format danych lub spróbować go obsługiwać. Jeśli dla dowolnego elementu danych został ustanowiony ciepły link, serwer może obsługiwać tylko jeden format danych w danym momencie. Wynika to z faktu, że komunikat WM_DDE_DATA dla ciepłej łączności ma dojście danych NULL, które w przeciwnym razie zawierałoby informacje o formacie. W związku z tym serwer musi odrzucić wszystkie ciepłe linki dla elementu już połączonego i musi odrzucić wszystkie linki dla elementu, który ma ciepłe linki. Inną interpretacją może być to, że serwer zmienia format i gorący lub ciepły stan łącza, gdy drugi link jest żądany dla tego samego elementu danych.

Ogólnie rzecz biorąc, aplikacje klienckie nie powinny podejmować próby ustanowienia więcej niż jednego linku jednocześnie dla elementu danych.

Aplikacje, które obsługują gorące lub ciepłe łącza danych, zwykle obsługują zarejestrowany format schowka o nazwie Link. Po skojarzeniu z poleceniami Kopiuj i Wklej łącze aplikacji ten format schowka umożliwia użytkownikowi ustanawianie konwersacji DDE między aplikacjami po prostu przez skopiowanie elementu danych w aplikacji serwera i wklejenie jej do aplikacji klienckiej.

Aplikacja serwera obsługuje format schowka Link, umieszczając w schowku ciąg zawierający nazwy aplikacji, tematu i elementu, gdy użytkownik wybierze polecenie "Kopiuj" z menu "Edytuj" . Poniżej znajduje się standardowy format linku:

aplikacja**\0tematu\0elementu\0\0**

Pojedynczy znak o wartości null oddziela nazwy, a dwa znaki null kończą cały ciąg.

Zarówno aplikacje klienckie, jak i serwerowe muszą zarejestrować format schowka Link, jak pokazano poniżej:

cfLink = RegisterClipboardFormat("Link");

Aplikacja kliencka obsługuje format schowka Link za pomocą polecenia Wklej łącze w menu Edycja. Gdy użytkownik wybierze to polecenie, aplikacja kliencka analizuje nazwy aplikacji, tematu i elementów z danych schowka w formacie linku. Korzystając z tych nazw, aplikacja kliencka inicjuje konwersację dla aplikacji i tematu, jeśli taka konwersacja jeszcze nie istnieje. Następnie aplikacja kliencka wysyła komunikat WM_DDE_ADVISE do aplikacji serwera, określając nazwę elementu zawartą w danych schowka w formacie linków.

Poniżej przedstawiono przykład odpowiedzi aplikacji klienckiej, gdy użytkownik wybierze polecenie Wklej łącze.

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

W tym przykładzie aplikacja kliencka otwiera schowek i określa, czy zawiera dane w formacie cfLink, który wcześniej zarejestrowała. Jeśli nie, albo jeśli nie może zablokować danych w schowku, klient wraca.

Gdy aplikacja kliencka pobierze wskaźnik do danych schowka, analizuje dane w celu wyodrębnienia aplikacji, tematu i nazw elementów.

Aplikacja kliencka określa, czy konwersacja w temacie już istnieje między nim a aplikacją serwera. Jeśli konwersacja istnieje, klient sprawdza, czy link już istnieje dla elementu danych. Jeśli taki link istnieje, klient wyświetli użytkownikowi okno komunikatu; w przeciwnym razie wywołuje własną funkcję SendAdvise w celu wysłania komunikatu WM_DDE_ADVISE do serwera dla elementu.

Jeśli konwersacja na dany temat nie istnieje jeszcze między klientem a serwerem, klient najpierw wykorzystuje funkcję SendInitiate, aby wysłać komunikat WM_DDE_INITIATE w celu zażądania konwersacji, a następnie wykorzystuje funkcję FindServerGivenAppTopic, aby ustanowić rozmowę z oknem, które odpowiada w imieniu aplikacji serwera. Po rozpoczęciu konwersacji aplikacja kliencka wywołuje SendAdvise, aby zażądać linku.

Powiadamianie klienta o zmianie danych

Gdy klient ustanawia łącze przy użyciu komunikatu WM_DDE_ADVISE, a element członkowski fDeferUpd nie jest ustawiony (czyli równy zero) w strukturze DDEDATA, klient prosi, aby serwer wysyłał element danych za każdym razem, gdy wartość elementu ulegnie zmianie. W takich przypadkach serwer renderuje nową wartość elementu danych w wcześniej określonym formacie i wysyła klientowi komunikat WM_DDE_DATA, jak pokazano w poniższym przykładzie.

// 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. 
 
}

W tym przykładzie klient przetwarza odpowiednio wartość elementu. Jeśli ustawiono flagę fAckReq dla elementu, klient wysyła serwer komunikat dodatni WM_DDE_ACK.

Gdy klient nawiązuje połączenie, z ustawionym członem fDeferUpd (to znaczy równe 1), klient żąda, aby tylko powiadomienie, a nie same dane, były wysyłane za każdym razem, gdy dane się zmienią. W takich przypadkach, gdy wartość elementu zmienia się, serwer nie renderuje tej wartości, lecz po prostu wysyła klientowi komunikat WM_DDE_DATA z uchwytem danych równym null, jak pokazano w poniższym przykładzie.

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

W razie potrzeby klient może zażądać najnowszej wartości elementu danych, wydając normalny komunikat WM_DDE_REQUEST lub po prostu zignorować powiadomienie z serwera, że dane uległy zmianie. W obu przypadkach, jeśli fAckReq jest równa 1, klient ma wysłać dodatni komunikat WM_DDE_ACK do serwera.

Jeśli klient żąda zakończenia określonego linku danych, klient wysyła serwerowi komunikat WM_DDE_UNADVISE, jak pokazano w poniższym przykładzie.

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

Serwer sprawdza, czy klient ma obecnie link do określonego elementu w tej konwersacji. Jeśli istnieje link, serwer wysyła klientowi komunikat dodatni WM_DDE_ACK; serwer nie jest już wymagany do wysyłania aktualizacji dotyczących elementu. Jeśli link nie istnieje, serwer wysyła klientowi komunikat ujemny WM_DDE_ACK.

Komunikat WM_DDE_UNADVISE określa format danych. Format zero informuje serwer o zatrzymaniu wszystkich łączy dla określonego elementu, nawet jeśli zostanie ustanowionych kilka gorących łączy, a każdy z nich używa innego formatu.

Aby przerwać wszystkie linki do konwersacji, aplikacja kliencka wysyła serwerowi komunikat WM_DDE_UNADVISE z atomem elementu o wartości null. Serwer określa, czy konwersacja ma obecnie co najmniej jedno łącze. Jeśli istnieje link, serwer wysyła klientowi komunikat dodatni WM_DDE_ACK; serwer nie musi już wysyłać żadnych aktualizacji w konwersacji. Jeśli link nie istnieje, serwer wysyła klientowi komunikat ujemny WM_DDE_ACK.

Wykonywanie poleceń w aplikacji serwera

Aplikacje mogą używać komunikatu WM_DDE_EXECUTE, aby spowodować wykonanie określonego polecenia lub serii poleceń w innej aplikacji. W tym celu klient wysyła do serwera komunikat WM_DDE_EXECUTE, który zawiera uchwyt do ciągu polecenia, jak pokazano w poniższym przykładzie.

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

W tym przykładzie serwer próbuje wykonać określony ciąg polecenia. Jeśli to się powiedzie, serwer wysyła klientowi komunikat dodatni WM_DDE_ACK; w przeciwnym razie wysyła negatywną wiadomość WM_DDE_ACK. Ten komunikat WM_DDE_ACK ponownie używa hCommand dojścia przekazanego w oryginalnym komunikacie WM_DDE_EXECUTE.

Jeśli ciąg wykonywania polecenia klienta żąda zakończenia pracy serwera, serwer powinien odpowiedzieć przez wysłanie komunikatu dodatniego WM_DDE_ACK, a następnie wysłać komunikat WM_DDE_TERMINATE przed zakończeniem pracy. Wszystkie inne polecenia wysyłane za pomocą komunikatu WM_DDE_EXECUTE powinny być wykonywane synchronicznie; oznacza to, że serwer powinien wysłać komunikat WM_DDE_ACK dopiero po pomyślnym ukończeniu polecenia.

Kończenie konwersacji

Klient lub serwer może w dowolnym momencie wydać komunikat WM_DDE_TERMINATE w celu zakończenia konwersacji. Podobnie zarówno aplikacje klienckie, jak i serwerowe powinny być przygotowane do odbierania tego komunikatu w dowolnym momencie. Aplikacja musi zakończyć wszystkie konwersacje przed zamknięciem.

W poniższym przykładzie aplikacja kończąca konwersację publikuje komunikat WM_DDE_TERMINATE.

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

Informuje to drugą aplikację, że aplikacja wysyłająca nie będzie wysyłać żadnych dalszych komunikatów, a odbiorca może zamknąć okno. Odbiorca powinien we wszystkich przypadkach szybko odpowiedzieć, wysyłając wiadomość WM_DDE_TERMINATE. Odbiorca nie może wysłać wiadomości ujemnej, zajętej ani dodatniej WM_DDE_ACK.

Po wysłaniu przez aplikację wiadomości WM_DDE_TERMINATE do partnera w konwersacji DDE nie może ona odpowiadać na wiadomości od tego partnera, ponieważ partner mógł zniszczyć okno, do którego zostanie wysłana odpowiedź.

Jeśli aplikacja odbiera komunikat DDE inny niż WM_DDE_TERMINATE po opublikowaniu WM_DDE_TERMINATE, powinien zwolnić wszystkie obiekty skojarzone z odebranymi komunikatami, z wyjątkiem dojść danych dla komunikatów WM_DDE_DATA lub WM_DDE_POKE komunikatów, które nie mają zestaw elementów członkowskich fRelease.

Gdy aplikacja ma zakończyć działanie, powinna zakończyć wszystkie aktywne konwersacje DDE przed zakończeniem przetwarzania komunikatu WM_DESTROY. Jeśli jednak aplikacja nie zakończy aktywnych konwersacji DDE, system zakończy wszelkie konwersacje DDE skojarzone z oknem, gdy okno zostanie zniszczone. W poniższym przykładzie pokazano, jak aplikacja serwera kończy wszystkie konwersacje 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; 
}