Utilisation de Dynamic Data Exchange

Cette section contient des exemples de code sur les tâches suivantes :

Lancement d’une conversation

Pour lancer une conversation DDE (Dynamic Data Exchange), le client envoie un message WM_DDE_INITIATE . En règle générale, le client diffuse ce message en appelant SendMessage, avec –1 comme premier paramètre. Si l’application a déjà le handle de fenêtre pour l’application serveur, elle peut envoyer le message directement à cette fenêtre. Le client prépare atoms pour le nom de l’application et le nom de la rubrique en appelant GlobalAddAtom. Le client peut demander des conversations avec n’importe quelle application serveur potentielle et pour n’importe quelle rubrique potentielle en fournissant des atomes NULL (caractères génériques) pour l’application et la rubrique.

L’exemple suivant illustre la façon dont le client lance une conversation, où l’application et la rubrique sont spécifiées.

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

Notes

Si votre application utilise des atomes NULL , vous n’avez pas besoin d’utiliser les fonctions GlobalAddAtom et GlobalDeleteAtom . Dans cet exemple, l’application cliente crée deux atomes globaux contenant respectivement le nom du serveur et le nom de la rubrique.

 

L’application cliente envoie un message WM_DDE_INITIATE avec ces deux atomes dans le paramètre lParam du message. Dans l’appel à la fonction SendMessage , le handle de fenêtre spécial –1 indique au système d’envoyer ce message à toutes les autres applications actives. SendMessage ne retourne pas à l’application cliente tant que toutes les applications qui reçoivent le message ont, à leur tour, retourné le contrôle au système. Cela signifie que tous les messages WM_DDE_ACK envoyés en réponse par les applications serveur ont été traités par le client avant le retour de l’appel SendMessage .

Une fois SendMessage retourné, l’application cliente supprime les atomes globaux.

Les applications serveur répondent selon la logique illustrée dans le diagramme suivant.

logique de réponse de l’application serveur

Pour accepter une ou plusieurs rubriques, le serveur doit créer des atomes pour chaque conversation (nécessitant des atomes de nom d’application en double s’il existe plusieurs rubriques) et envoyer un message WM_DDE_ACK pour chaque conversation, comme illustré dans l’exemple suivant.

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

Lorsqu’un serveur répond avec un message WM_DDE_ACK , l’application cliente doit enregistrer un handle dans la fenêtre serveur. Le client recevant le handle en tant que paramètre wParam du message WM_DDE_ACK envoie ensuite tous les messages DDE suivants à la fenêtre de serveur que ce handle identifie.

Si votre application cliente utilise un atome NULL pour le nom de l’application ou le nom de la rubrique, attendez-vous à ce que l’application reçoive des accusés de réception de plusieurs applications serveur. Plusieurs accusés de réception peuvent également provenir de plusieurs instances d’un serveur DDE, même si votre application cliente n’utilise pas les atomes null. Un serveur doit toujours utiliser une fenêtre unique pour chaque conversation. La procédure de fenêtre dans l’application cliente peut utiliser un handle pour la fenêtre serveur (fourni en tant que paramètre lParam de WM_DDE_INITIATE) pour suivre plusieurs conversations. Cela permet à une seule fenêtre cliente de traiter plusieurs conversations sans avoir à s’arrêter et à se reconnecter avec une nouvelle fenêtre cliente pour chaque conversation.

Transfert d’un élément unique

Une fois qu’une conversation DDE a été établie, le client peut récupérer la valeur d’un élément de données à partir du serveur en émettant le message WM_DDE_REQUEST , ou envoyer une valeur d’élément de données au serveur en émettant WM_DDE_POKE.

Récupération d’un élément à partir du serveur

Pour récupérer un élément à partir du serveur, le client envoie au serveur un message WM_DDE_REQUEST spécifiant l’élément et le format à récupérer, comme illustré dans l’exemple suivant.

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

Dans cet exemple, le client spécifie le format du Presse-papiers CF_TEXT comme format préféré pour l’élément de données demandé.

Le récepteur (serveur) du message WM_DDE_REQUEST doit généralement supprimer l’atome d’élément, mais si l’appel PostMessage échoue, le client doit supprimer l’atome.

Si le serveur a accès à l’élément demandé et peut le restituer dans le format demandé, il copie la valeur de l’élément en tant qu’objet de mémoire partagée et envoie au client un message WM_DDE_DATA , comme illustré dans l’exemple suivant.

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

Dans cet exemple, l’application serveur alloue un objet mémoire pour contenir l’élément de données. L’objet de données est initialisé en tant que structure DDEDATA .

L’application serveur définit ensuite le membre cfFormat de la structure sur CF_TEXT pour informer l’application cliente que les données sont au format texte. Le client répond en copiant la valeur des données demandées dans le membre Value de la structure DDEDATA . Une fois que le serveur a rempli l’objet de données, le serveur déverrouille les données et crée un atome global contenant le nom de l’élément de données.

Enfin, le serveur émet le message WM_DDE_DATA en appelant PostMessage. Le handle de l’objet de données et l’atome contenant le nom de l’élément sont empaquetés dans le paramètre lParam du message par la fonction PackDDElParam .

En cas d’échec de PostMessage , le serveur doit utiliser la fonction FreeDDElParam pour libérer le paramètre lParam packé. Le serveur doit également libérer le paramètre lParam packé pour le message WM_DDE_REQUEST qu’il a reçu.

Si le serveur ne peut pas satisfaire la demande, il envoie un message de WM_DDE_ACK négatif au client, comme illustré dans l’exemple suivant.

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

Lors de la réception d’un message WM_DDE_DATA , le client traite la valeur de l’élément de données comme il convient. Ensuite, si le membre fAckReq pointé dans le message WM_DDE_DATA a la valeur 1, le client doit envoyer au serveur un message de WM_DDE_ACK positif, comme illustré dans l’exemple suivant.

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

Dans cet exemple, le client examine le format des données. Si le format n’est pas CF_TEXT (ou si le client ne peut pas verrouiller la mémoire des données), le client envoie un message de WM_DDE_ACK négatif pour indiquer qu’il ne peut pas traiter les données. Si le client ne peut pas verrouiller un handle de données parce qu’il contient le membre fAckReq , le client ne doit pas envoyer un message de WM_DDE_ACK négatif. Au lieu de cela, le client doit mettre fin à la conversation.

Si un client envoie un accusé de réception négatif en réponse à un message WM_DDE_DATA , le serveur est chargé de libérer la mémoire (mais pas le paramètre lParam ) référencé par le message WM_DDE_DATA associé à l’accusé de réception négatif.

S’il peut traiter les données, le client examine le membre fAckReq de la structure DDEDATA pour déterminer si le serveur a demandé qu’il soit informé que le client a reçu et traité les données avec succès. Si le serveur a demandé ces informations, le client envoie au serveur un message de WM_DDE_ACK positif.

Étant donné que le déverrouillage des données invalide le pointeur vers les données, le client enregistre la valeur du membre fRelease avant de déverrouiller l’objet de données. Après avoir enregistré la valeur, le client l’examine pour déterminer si l’application serveur a demandé au client de libérer la mémoire contenant les données ; le client agit en conséquence.

Lors de la réception d’un message WM_DDE_ACK négatif, le client peut demander à nouveau la même valeur d’élément, en spécifiant un format de Presse-papiers différent. En règle générale, un client demande d’abord le format le plus complexe qu’il peut prendre en charge, puis descend si nécessaire en utilisant des formats progressivement plus simples jusqu’à ce qu’il en trouve un que le serveur peut fournir.

Si le serveur prend en charge l’élément Formats de la rubrique système, le client peut déterminer une fois les formats du Presse-papiers pris en charge par le serveur, au lieu de les déterminer chaque fois que le client demande un élément.

Envoi d’un élément au serveur

Le client peut envoyer une valeur d’élément au serveur à l’aide du message WM_DDE_POKE . Le client restitue l’élément à envoyer et envoie le message WM_DDE_POKE , comme illustré dans l’exemple suivant.

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

Notes

L’envoi de données à l’aide d’un message WM_DDE_POKE est essentiellement identique à l’envoi de données à l’aide de WM_DDE_DATA, sauf que WM_DDE_POKE est envoyé du client au serveur.

 

Si le serveur est en mesure d’accepter la valeur d’élément de données au format rendu par le client, le serveur traite la valeur de l’élément comme il convient et envoie au client un message de WM_DDE_ACK positif. S’il ne parvient pas à traiter la valeur de l’élément, en raison de son format ou pour d’autres raisons, le serveur envoie au client un message de WM_DDE_ACK négatif.

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.

Dans cet exemple, le serveur appelle GlobalGetAtomName pour récupérer le nom de l’élément envoyé par le client. Le serveur détermine ensuite s’il prend en charge l’élément et si l’élément est rendu dans le format correct (c’est-à-dire CF_TEXT). Si l’élément n’est pas pris en charge et n’est pas rendu dans le format approprié, ou si le serveur ne peut pas verrouiller la mémoire des données, le serveur envoie un accusé de réception négatif à l’application cliente. Notez que dans ce cas, l’envoi d’un accusé de réception négatif est correct , car WM_DDE_POKE messages sont toujours supposés avoir le membre fAckReq défini. Le serveur doit ignorer le membre.

Si un serveur envoie un accusé de réception négatif en réponse à un message WM_DDE_POKE , le client est chargé de libérer la mémoire (mais pas le paramètre lParam ) référencé par le message WM_DDE_POKE associé à l’accusé de réception négatif.

Une application cliente peut utiliser DDE pour établir un lien vers un élément dans une application serveur. Une fois ce lien établi, le serveur envoie des mises à jour périodiques de l’élément lié au client, généralement chaque fois que la valeur de l’élément change. Ainsi, un flux de données permanent est établi entre les deux applications ; ce flux de données reste en place jusqu’à ce qu’il soit explicitement déconnecté.

Le client lance un lien de données en publiant un message WM_DDE_ADVISE , comme illustré dans l’exemple suivant.

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 
 
}

Dans cet exemple, l’application cliente définit l’indicateur fDeferUpd du message WM_DDE_ADVISE sur FALSE. Cela indique à l’application serveur d’envoyer les données au client chaque fois que les données changent.

Si le serveur ne parvient pas à traiter la demande de WM_DDE_ADVISE , il envoie au client un message de WM_DDE_ACK négatif. Mais si le serveur a accès à l’élément et peut le restituer dans le format demandé, le serveur note le nouveau lien (rappelant les indicateurs spécifiés dans le paramètre hOptions ) et envoie au client un message de WM_DDE_ACK positif. À partir de là, jusqu’à ce que le client émet un message de WM_DDE_UNADVISE correspondant, le serveur envoie les nouvelles données au client chaque fois que la valeur de l’élément change dans le serveur.

Le message WM_DDE_ADVISE établit le format des données à échanger pendant le lien. Si le client tente d’établir un autre lien avec le même élément mais utilise un format de données différent, le serveur peut choisir de rejeter le deuxième format de données ou de tenter de le prendre en charge. Si un lien chaud a été établi pour un élément de données, le serveur ne peut prendre en charge qu’un seul format de données à la fois. Cela est dû au fait que le message WM_DDE_DATA d’un lien chaud a un handle de données NULL , qui contient sinon les informations de format. Ainsi, un serveur doit rejeter tous les liens chauds pour un élément déjà lié, et doit rejeter tous les liens pour un élément qui a des liens chauds. Une autre interprétation peut être que le serveur modifie le format et l’état chaud ou chaud d’un lien lorsqu’un deuxième lien est demandé pour le même élément de données.

En général, les applications clientes ne doivent pas tenter d’établir plusieurs liens à la fois pour un élément de données.

Les applications qui prennent en charge les liaisons de données chaudes ou chaudes prennent généralement en charge un format de Presse-papiers inscrit nommé Link. Lorsqu’il est associé aux commandes Copier et coller un lien de l’application, ce format de presse-papiers permet à l’utilisateur d’établir des conversations DDE entre des applications simplement en copiant un élément de données dans l’application serveur et en le collant dans l’application cliente.

Une application serveur prend en charge le format du Presse-papiers Link en plaçant dans le Presse-papiers une chaîne contenant les noms d’application, de rubrique et d’élément lorsque l’utilisateur choisit la commande Copier dans le menu Modifier . Voici le format de lien standard :

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

Un seul caractère Null sépare les noms, et deux caractères Null mettent fin à la chaîne entière.

Les applications clientes et serveurs doivent inscrire le format du Presse-papiers Link, comme indiqué :

cfLink = RegisterClipboardFormat("Link");

Une application cliente prend en charge le format du Presse-papiers Link au moyen d’une commande Coller un lien dans son menu Edition. Lorsque l’utilisateur choisit cette commande, l’application cliente analyse les noms d’application, de rubrique et d’élément à partir des données du Presse-papiers au format lien. À l’aide de ces noms, l’application cliente lance une conversation pour l’application et la rubrique, si une telle conversation n’existe pas déjà. L’application cliente envoie ensuite un message WM_DDE_ADVISE à l’application serveur, en spécifiant le nom de l’élément contenu dans les données du Presse-papiers au format lien.

Voici un exemple de réponse d’une application cliente lorsque l’utilisateur choisit la commande Coller le lien.

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

Dans cet exemple, l’application cliente ouvre le Presse-papiers et détermine s’il contient des données au format Link (cfLink) qu’elle avait précédemment inscrite. Si ce n’est pas le cas, ou s’il ne peut pas verrouiller les données dans le Presse-papiers, le client retourne.

Une fois que l’application cliente a récupéré un pointeur vers les données du Presse-papiers, elle analyse les données pour extraire les noms d’application, de rubrique et d’élément.

L’application cliente détermine si une conversation sur le sujet existe déjà entre elle et l’application serveur. S’il existe une conversation, le client vérifie si un lien existe déjà pour l’élément de données. S’il existe un tel lien, le client affiche une boîte de message à l’utilisateur ; sinon, il appelle sa propre fonction SendAdvise pour envoyer un message WM_DDE_ADVISE au serveur pour l’élément.

S’il n’existe pas encore de conversation sur le sujet entre le client et le serveur, le client appelle d’abord sa propre fonction SendInitiate pour diffuser le message WM_DDE_INITIATE pour demander une conversation, puis appelle sa propre fonction FindServerGivenAppTopic pour établir la conversation avec la fenêtre qui répond au nom de l’application serveur. Une fois la conversation commencée, l’application cliente appelle SendAdvise pour demander le lien.

Notification au client que les données ont changé

Lorsque le client établit un lien à l’aide du message WM_DDE_ADVISE , avec le membre fDeferUpd non défini (c’est-à-dire égal à zéro) dans la structure DDEDATA , le client a demandé au serveur d’envoyer l’élément de données chaque fois que la valeur de l’élément change. Dans ce cas, le serveur restitue la nouvelle valeur de l’élément de données dans le format spécifié précédemment et envoie au client un message WM_DDE_DATA , comme illustré dans l’exemple suivant.

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

Dans cet exemple, le client traite la valeur de l’élément comme il convient. Si l’indicateur fAckReq pour l’élément est défini, le client envoie au serveur un message de WM_DDE_ACK positif.

Lorsque le client établit le lien, avec le membre fDeferUpd défini (c’est-à-dire égal à 1), le client a demandé que seule une notification, et non les données elles-mêmes, soit envoyée chaque fois que les données changent. Dans ce cas, lorsque la valeur de l’élément change, le serveur ne rend pas la valeur, mais envoie simplement au client un message WM_DDE_DATA avec un handle de données Null, comme illustré dans l’exemple suivant.

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

Si nécessaire, le client peut demander la valeur la plus récente de l’élément de données en émettant un message de WM_DDE_REQUEST normal, ou il peut simplement ignorer l’avis du serveur indiquant que les données ont changé. Dans les deux cas, si fAckReq est égal à 1, le client est censé envoyer un message WM_DDE_ACK positif au serveur.

Si le client demande l’arrêt d’un lien de données spécifique, le client envoie au serveur un message WM_DDE_UNADVISE , comme illustré dans l’exemple suivant.

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

Le serveur vérifie si le client dispose actuellement d’un lien vers l’élément spécifique dans cette conversation. S’il existe un lien, le serveur envoie au client un message de WM_DDE_ACK positif ; le serveur n’est alors plus tenu d’envoyer des mises à jour sur l’élément. S’il n’existe aucun lien, le serveur envoie au client un message de WM_DDE_ACK négatif.

Le message WM_DDE_UNADVISE spécifie un format de données. Un format de zéro indique au serveur d’arrêter tous les liens pour l’élément spécifié, même si plusieurs liens sont établis et que chacun utilise un format différent.

Pour arrêter tous les liens d’une conversation, l’application cliente envoie au serveur un message WM_DDE_UNADVISE avec un atome d’élément null. Le serveur détermine si la conversation a au moins un lien établi. S’il existe un lien, le serveur envoie au client un message de WM_DDE_ACK positif ; le serveur n’a plus besoin d’envoyer de mises à jour dans la conversation. S’il n’existe aucun lien, le serveur envoie au client un message de WM_DDE_ACK négatif.

Exécution de commandes dans une application serveur

Les applications peuvent utiliser le message WM_DDE_EXECUTE pour entraîner l’exécution d’une commande ou d’une série de commandes dans une autre application. Pour ce faire, le client envoie au serveur un message WM_DDE_EXECUTE contenant un handle à une chaîne de commande, comme illustré dans l’exemple suivant.

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

Dans cet exemple, le serveur tente d’exécuter la chaîne de commande spécifiée. S’il réussit, le serveur envoie au client un message de WM_DDE_ACK positif ; sinon, il envoie un message WM_DDE_ACK négatif. Ce message WM_DDE_ACK réutilise le handle hCommand passé dans le message WM_DDE_EXECUTE d’origine .

Si la chaîne d’exécution de commande du client demande l’arrêt du serveur, le serveur doit répondre en envoyant un message de WM_DDE_ACK positif, puis publier un message WM_DDE_TERMINATE avant de se terminer. Toutes les autres commandes envoyées avec un message WM_DDE_EXECUTE doivent être exécutées de manière synchrone ; Autrement dit, le serveur ne doit envoyer un message WM_DDE_ACK qu’après avoir correctement terminé la commande.

Fin d’une conversation

Le client ou le serveur peut émettre un message WM_DDE_TERMINATE pour mettre fin à une conversation à tout moment. De même, les applications clientes et serveurs doivent être prêtes à recevoir ce message à tout moment. Une application doit mettre fin à toutes ses conversations avant de s’arrêter.

Dans l’exemple suivant, l’application qui termine la conversation publie un message WM_DDE_TERMINATE .

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

Cela informe l’autre application que l’application émettrice n’enverra aucun autre message et que le destinataire peut fermer sa fenêtre. Dans tous les cas, le destinataire doit répondre rapidement en envoyant un message WM_DDE_TERMINATE . Le destinataire ne doit pas envoyer un message de WM_DDE_ACK négatif, occupé ou positif.

Une fois qu’une application a envoyé le message WM_DDE_TERMINATE au partenaire dans une conversation DDE, elle ne doit pas répondre aux messages de ce partenaire, car le partenaire a peut-être détruit la fenêtre à laquelle la réponse serait envoyée.

Si une application reçoit un message DDE autre que WM_DDE_TERMINATE après avoir publié WM_DDE_TERMINATE, elle doit libérer tous les objets associés aux messages reçus, à l’exception des handles de données pour les messages WM_DDE_DATA ou WM_DDE_POKE qui n’ont pas le membre fRelease défini.

Lorsqu’une application est sur le point de se terminer, elle doit mettre fin à toutes les conversations DDE actives avant de terminer le traitement du message WM_DESTROY . Toutefois, si une application ne met pas fin à ses conversations DDE actives, le système met fin à toutes les conversations DDE associées à une fenêtre lorsque la fenêtre est détruite. L’exemple suivant montre comment une application serveur met fin à toutes les conversations 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; 
}