Verwenden von dynamische Daten Exchange
Dieser Abschnitt enthält Codebeispiele für die folgenden Aufgaben:
- Initiieren einer Konversation
- Übertragen eines einzelnen Elements
- Einrichten eines permanenten Datenlinks
- Ausführen von Befehlen in einer Serveranwendung
- Beenden einer Konversation
Initiieren einer Konversation
Um eine DDE-Konversation (dynamische Daten Exchange) zu initiieren, sendet der Client eine WM _ DDE _ INITIATE-Nachricht. In der Regel überträgt der Client diese Nachricht, indem er SendMessage aufruft,wobei –1 als erster Parameter verwendet wird. Wenn die Anwendung bereits über das Fensterhandle für die Serveranwendung verfügt, kann sie die Nachricht direkt an dieses Fenster senden. Der Client bereitet Atome für den Anwendungsnamen und den Themennamen vor, indem globalAddAtomaufgerufen wird. Der Client kann Konversationen mit jeder potenziellen Serveranwendung und für ein beliebiges potenzielles Thema anfordern, indem er NULL-Atome (Platzhalter) für die Anwendung und das Thema anfordert.
Im folgenden Beispiel wird veranschaulicht, wie der Client eine Konversation initiiert, in der sowohl die Anwendung als auch das Thema angegeben werden.
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);
Hinweis
Wenn Ihre Anwendung NULL-Atome verwendet, müssen Sie nicht die Funktionen GlobalAddAtom und GlobalDeleteAtom verwenden. In diesem Beispiel erstellt die Clientanwendung zwei globale Atome, die den Namen des Servers bzw. den Namen des Themas enthalten.
Die Clientanwendung sendet eine WM _ DDE _ INITIATE-Nachricht mit diesen beiden Atomen im lParam-Parameter der Nachricht. Im Aufruf der SendMessage-Funktion weist das spezielle Fensterhandle –1 das System an, diese Nachricht an alle anderen aktiven Anwendungen zu senden. SendMessage kehrt erst dann zur Clientanwendung zurück, wenn alle Anwendungen, die die Nachricht empfangen, wiederum die Steuerung an das System zurückgegeben haben. Dies bedeutet, dass alle WM _ _ DDE-ACK-Nachrichten, die als Antwort von den Serveranwendungen gesendet werden, garantiert vom Client verarbeitet wurden, wenn der SendMessage-Aufruf zurückgegeben wurde.
Nachdem SendMessage zurückgegeben wurde, löscht die Clientanwendung die globalen Atome.
Serveranwendungen reagieren gemäß der im folgenden Diagramm dargestellten Logik.

Um ein oder mehrere Themen zu bestätigen, muss der Server Atome für jede Konversation erstellen (wobei doppelte Anwendungsnamen-Atome erforderlich sind, wenn mehrere Themen vorhanden sind) und eine WM _ _ DDE-ACK-Nachricht für jede Konversation senden, wie im folgenden Beispiel veranschaulicht.
if ((atomApplication = GlobalAddAtom("Server")) != 0)
{
if ((atomTopic = GlobalAddAtom(szTopic)) != 0)
{
SendMessage(hwndClientDDE,
WM_DDE_ACK,
(WPARAM) hwndServerDDE,
MAKELONG(atomApplication, atomTopic));
GlobalDeleteAtom(atomApplication);
}
GlobalDeleteAtom(atomTopic);
}
if ((atomApplication == 0) || (atomTopic == 0))
{
// Handle errors.
}
Wenn ein Server mit einer WM _ _ DDE-ACK-Meldung antwortet, sollte die Clientanwendung ein Handle im Serverfenster speichern. Der Client, der das Handle als wParam-Parameter der WM _ _ DDE-ACK-Nachricht empfängt, sendet dann alle nachfolgenden DDE-Nachrichten an das Serverfenster, das dieses Handle identifiziert.
Wenn Ihre Clientanwendung ein NULL-Atom für den Anwendungsnamen oder Themennamen verwendet, erwarten Sie, dass die Anwendung Bestätigungen von mehr als einer Serveranwendung empfängt. Mehrere Bestätigungen können auch von mehreren Instanzen eines DDE-Servers stammen, auch wenn Ihre Clientanwendung keine ATOM-Werte verwendet. Ein Server sollte immer ein eindeutiges Fenster für jede Konversation verwenden. Die Fensterprozedur in der Clientanwendung kann ein Handle für das Serverfenster (bereitgestellt als lParam-Parameter von WM _ DDE _ INITIATE)verwenden, um mehrere Konversationen nachzuverfolgen. Dadurch kann ein einzelnes Clientfenster mehrere Konversationen verarbeiten, ohne dass für jede Konversation beendet und erneut eine Verbindung mit einem neuen Clientfenster hergestellt werden muss.
Übertragen eines einzelnen Elements
Sobald eine DDE-Konversation eingerichtet wurde, kann der Client entweder den Wert eines Datenelements vom Server abrufen, indem er die WM _ DDE _ REQUEST-Nachricht ausgibt, oder einen Datenelementwert an den Server übermitteln, indem WM _ _ DDEKONSTANTEEausgegeben wird.
Abrufen eines Elements vom Server
Um ein Element vom Server abzurufen, sendet der Client dem Server eine WM _ DDE _ REQUEST-Nachricht, die das abzurufende Element und Format angibt, wie im folgenden Beispiel gezeigt.
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.
}
In diesem Beispiel gibt der Client das Zwischenablageformat CF _ TEXT als bevorzugtes Format für das angeforderte Datenelement an.
Der Empfänger (Server) der WM _ DDE _ REQUEST-Nachricht muss in der Regel das Element atom löschen, aber wenn der PostMessage-Aufruf fehlschlägt, muss der Client das Atom löschen.
Wenn der Server Zugriff auf das angeforderte Element hat und es im angeforderten Format rendern kann, kopiert der Server den Elementwert als Shared Memory-Objekt und sendet dem Client eine WM _ DDE _ DATA-Nachricht, wie im folgenden Beispiel veranschaulicht.
// 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.
}
In diesem Beispiel ordnet die Serveranwendung ein Speicherobjekt zu, das das Datenelement enthalten soll. Das Datenobjekt wird als DDEDATA-Struktur initialisiert.
Die Serveranwendung legt dann den cfFormat-Member der -Struktur auf CF _ TEXT fest, um die Clientanwendung darüber zu informieren, dass die Daten im Textformat vorliegen. Der Client antwortet, indem er den Wert der angeforderten Daten in den Value-Member der DDEDATA-Struktur kopiert. Nachdem der Server das Datenobjekt ausgefüllt hat, entsperrt der Server die Daten und erstellt ein globales Atom, das den Namen des Datenelements enthält.
Schließlich gibt der Server die WM _ DDE _ DATA-Nachricht aus, indem er PostMessage aufruft. Das Handle für das Datenobjekt und das Atom, das den Elementnamen enthält, werden von der PackDDElParam-Funktion in den lParam-Parameter der Nachricht gepackt.
Wenn Bei PostMessage ein Fehler auftritt, muss der Server die FreeDDElParam-Funktion verwenden, um den gepackten lParam-Parameter freizuladen. Der Server muss auch den gepackten lParam-Parameter für die empfangene WM _ DDE _ REQUEST-Nachricht freigeben.
Wenn der Server die Anforderung nicht erfüllen kann, sendet er eine negative WM _ DDE _ ACK-Nachricht an den Client, wie im folgenden Beispiel gezeigt.
// Negative acknowledgment.
PostMessage(hwndClientDDE,
WM_DDE_ACK,
(WPARAM) hwndServerDDE,
PackDDElParam(WM_DDE_ACK, 0, atomItem));
Nach dem Empfang einer WM _ DDE _ DATA-Nachricht verarbeitet der Client den Datenelementwert nach Bedarf. Wenn das fAckReq-Element, auf das in der WM _ DDE _ DATA-Nachricht verwiesen wird, 1 ist, muss der Client eine positive WM _ _ DDE-ACK-Nachricht an den Server senden, wie im folgenden Beispiel gezeigt.
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);
In diesem Beispiel überprüft der Client das Format der Daten. Wenn das Format nicht CF _ TEXT ist (oder wenn der Client den Arbeitsspeicher für die Daten nicht sperren kann), sendet der Client eine negative WM _ _ DDE-ACK-Nachricht, um anzugeben, dass die Daten nicht verarbeitet werden können. Wenn der Client ein Datenhandle nicht sperren kann, weil das Handle den fAckReq-Member enthält, sollte der Client keine negative WM _ _ DDE-ACK-Nachricht senden. Stattdessen sollte der Client die Konversation beenden.
Wenn ein Client als Reaktion auf eine WM _ DDE _ DATA-Nachricht eine negative Bestätigung sendet, ist der Server für die Speicherbelegung (aber nicht für den lParam-Parameter) verantwortlich, auf den von der WM _ DDE _ DATA-Nachricht verwiesen wird, die der negativen Bestätigung zugeordnet ist.
Wenn die Daten verarbeitet werden können, überprüft der Client den fAckReq-Member der DDEDATA-Struktur, um zu ermitteln, ob der Server angefordert hat, dass er darüber informiert wurde, dass der Client die Daten erfolgreich empfangen und verarbeitet hat. Wenn der Server diese Informationen angefordert hat, sendet der Client eine positive WM _ _ DDE-ACK-Nachricht an den Server.
Da das Entsperren von Daten den Zeiger auf die Daten ungültig macht, speichert der Client den Wert des fRelease-Members, bevor das Datenobjekt entsperrt wird. Nach dem Speichern des Werts überprüft der Client ihn, um zu bestimmen, ob die Serveranwendung den Client aufgefordert hat, den Arbeitsspeicher freizugeben, der die Daten enthält. Der Client verhält sich entsprechend.
Nach dem Empfang einer negativen WM _ _ DDE-ACK-Nachricht kann der Client erneut denselben Elementwert anfordern und dabei ein anderes Zwischenablageformat angeben. In der Regel fragt ein Client zunächst nach dem komplexesten Format, das er unterstützen kann, und führt dann bei Bedarf schrittweise einfachere Formate aus, bis er eines findet, das der Server bereitstellen kann.
Wenn der Server das Formatelement des Systemthemas unterstützt, kann der Client bestimmen, welche Zwischenablageformate der Server unterstützt, anstatt sie jedes Mal zu bestimmen, wenn der Client ein Element anfordert.
Übermitteln eines Elements an den Server
Der Client kann einen Elementwert mithilfe der WM _ _ DDE-NACHRICHT (WM DDE) an den Server senden. Der Client rendert das zu sendende Element und sendet die WM _ _ DDE-NACHRICHTENMELDUNG, wie im folgenden Beispiel veranschaulicht.
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.
}
Hinweis
Das Senden von Daten mithilfe einer WM _ _ DDE-MESSAGEE-Nachricht entspricht im Wesentlichen dem Senden mit WM _ DDE _ DATA,mit der Ausnahme, dass WM _ DDE _ LVE vom Client an den Server gesendet wird.
Wenn der Server den Datenelementwert in dem vom Client gerenderten Format akzeptieren kann, verarbeitet der Server den Elementwert nach Bedarf und sendet dem Client eine positive WM _ _ DDE-ACK-Nachricht. Wenn der Elementwert aufgrund seines Formats oder aus anderen Gründen nicht verarbeitet werden kann, sendet der Server dem Client eine negative WM _ _ DDE-ACK-Nachricht.
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.
In diesem Beispiel ruft der Server GlobalGetAtomName auf, um den Namen des Vom Client gesendeten Elements abzurufen. Der Server bestimmt dann, ob er das Element unterstützt und ob das Element im richtigen Format (cf text) gerendert _ wird. Wenn das Element nicht unterstützt und nicht im richtigen Format gerendert wird oder der Server den Arbeitsspeicher für die Daten nicht sperren kann, sendet der Server eine negative Bestätigung zurück an die Clientanwendung. Beachten Sie, dass in diesem Fall das Senden einer negativen Bestätigung richtig ist, da bei WM _ _ DDE-MELDUNGEN IMMER davon ausgegangen wird, dass der fAckReq-Member festgelegt ist. Der Server sollte das Element ignorieren.
Wenn ein Server eine negative Bestätigung als Antwort auf eine _ WM-DDE-MESSAGE _ SENDET, ist der Client dafür verantwortlich, den Arbeitsspeicher (aber nicht den lParam-Parameter) freizugeben, auf den von der WM _ _ DDE-NACHRICHT VOM TYP DER WM verwiesen wird, die der negativen Bestätigung zugeordnet ist.
Einrichten eines permanenten Datenlinks
Eine Clientanwendung kann DDE verwenden, um einen Link zu einem Element in einer Serveranwendung herzustellen. Nachdem ein solcher Link eingerichtet wurde, sendet der Server regelmäßig Updates des verknüpften Elements an den Client, in der Regel immer dann, wenn sich der Wert des Elements ändert. Daher wird zwischen den beiden Anwendungen ein permanenter Datenstrom eingerichtet. Dieser Datenstrom bleibt so lange bestehen, bis er explizit getrennt wird.
Initiieren eines Datenlinks
Der Client initiiert einen Datenlink, indem er wie im folgenden Beispiel gezeigt eine WM _ DDE _ ADVISE-Nachricht einsinget.
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
}
In diesem Beispiel legt die Clientanwendung das fDeferUpd-Flag der WM _ DDE _ ADVISE-Nachricht auf FALSE fest. Dies leitet die Serveranwendung an, die Daten an den Client zu senden, wenn sich die Daten ändern.
Wenn der Server die WM _ DDE _ ADVISE-Anforderung nicht ausführen kann, sendet er dem Client eine negative WM _ DDE _ ACK-Nachricht. Wenn der Server jedoch Zugriff auf das Element hat und es im angeforderten Format rendern kann, notiert der Server den neuen Link (unter Rückung der im hOptions-Parameter angegebenen Flags) und sendet dem Client eine positive WM _ DDE _ ACK-Nachricht. Von diesem Zeitpunkt an sendet der Server die neuen Daten jedes Mal an den Client, wenn sich der Wert des Elements auf dem Server ändert, bis der Client eine entsprechende WM _ DDE _ UNADVISE-Nachricht aus gibt.
Die WM _ DDE _ ADVISE-Nachricht legt das Format der Daten fest, die während des Links ausgetauscht werden sollen. Wenn der Client versucht, einen anderen Link mit demselben Element herzustellen, aber ein anderes Datenformat verwendet, kann der Server das zweite Datenformat ablehnen oder versuchen, es zu unterstützen. Wenn für ein Datenelement ein warmer Link eingerichtet wurde, kann der Server nur ein Datenformat gleichzeitig unterstützen. Dies liegt daran, dass die WM _ DDE _ DATA-Nachricht für einen warmen Link über ein NULL-Datenhand handle verfügt, das andernfalls die Formatinformationen enthält. Daher muss ein Server alle warmen Links für ein Element ablehnen, das bereits verknüpft ist, und alle Links für ein Element ablehnen, das über warme Links verfügt. Eine andere Interpretation kann sein, dass der Server das Format und den heißen oder warmen Zustand eines Links ändert, wenn ein zweiter Link für dasselbe Datenelement angefordert wird.
Im Allgemeinen sollten Clientanwendungen nicht versuchen, mehrere Links gleichzeitig für ein Datenelement zu erstellen.
Initiieren eines Datenlinks mit dem Befehl "Link einfügen"
Anwendungen, die hot- oder warm-Datenlinks unterstützen, unterstützen in der Regel ein registriertes Zwischenablageformat namens Link. Wenn es den Befehlen Copy and Paste Link der Anwendung zugeordnet ist, ermöglicht dieses Zwischenablageformat dem Benutzer, DDE-Konversationen zwischen Anwendungen herzustellen, indem einfach ein Datenelement in der Serveranwendung kopiert und in die Clientanwendung eingefügt wird.
Eine Serveranwendung unterstützt das Format der Link-Zwischenablage, indem sie in der Zwischenablage eine Zeichenfolge mit den Namen der Anwendung, des Themas und des Elements platziert, wenn der Benutzer im Menü Bearbeiten den Befehl Kopieren auswählt. Im Folgenden finden Sie das Standardmäßige Linkformat:
application**\ 0**topic\ 0item\ 0 \ 0
Ein einzelnes NULL-Zeichen trennt die Namen, und zwei NULL-Zeichen beenden die gesamte Zeichenfolge.
Sowohl die Client- als auch die Serveranwendung müssen das Format der Link-Zwischenablage registrieren, wie gezeigt:
cfLink = RegisterClipboardFormat("Link");
Eine Clientanwendung unterstützt das Format der Link-Zwischenablage mit einem Link einfügen-Befehl im Menü Bearbeiten. Wenn der Benutzer diesen Befehl auswählt, analysiert die Clientanwendung die Anwendungs-, Themen- und Elementnamen aus den Daten der Linkformat-Zwischenablage. Mit diesen Namen initiiert die Clientanwendung eine Konversation für die Anwendung und das Thema, wenn eine solche Konversation noch nicht vorhanden ist. Die Clientanwendung sendet dann eine WM _ DDE _ ADVISE-Nachricht an die Serveranwendung und gibt dabei den Elementnamen an, der in den Daten der Linkformat-Zwischenablage enthalten ist.
Es folgt ein Beispiel für die Antwort einer Clientanwendung, wenn der Benutzer den Befehl Link einfügen auswählt.
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;
}
In diesem Beispiel öffnet die Clientanwendung die Zwischenablage und bestimmt, ob sie Daten im Zuvor registrierten Linkformat (d. h. cfLink) enthält. Wenn dies nicht der Fehler ist oder die Daten nicht in der Zwischenablage gesperrt werden können, gibt der Client zurück.
Nachdem die Clientanwendung einen Zeiger auf die Zwischenablagedaten abgerufen hat, analysiert sie die Daten, um die Anwendungs-, Themen- und Elementnamen zu extrahieren.
Die Clientanwendung bestimmt, ob zwischen ihr und der Serveranwendung bereits eine Konversation zum Thema vorhanden ist. Wenn eine Konversation vorhanden ist, überprüft der Client, ob bereits ein Link für das Datenelement vorhanden ist. Wenn ein solcher Link vorhanden ist, zeigt der Client dem Benutzer ein Meldungsfeld an. Andernfalls ruft er seine eigene SendAdvise-Funktion auf, um eine WM _ DDE _ ADVISE-Nachricht für das Element an den Server zu senden.
Wenn zwischen dem Client und dem Server noch keine Konversation zum Thema vorhanden ist, ruft der Client zunächst seine eigene SendInitiate-Funktion auf, um die WM _ DDE _ INITIATE-Nachricht zum Anfordern einer Konversation zu senden. Zweitens ruft er seine eigene FindServerGivenAppTopic-Funktion auf, um die Konversation mit dem Fenster herzustellen, das im Namen der Serveranwendung antwortet. Nachdem die Konversation begonnen hat, ruft die Clientanwendung SendAdvise auf, um den Link an fordern.
Benachrichtigen des Clients über geänderte Daten
Wenn der Client mithilfe der WM _ DDE _ ADVISE-Nachricht einen Link ein richtet, bei dem das fDeferUpd-Element in der DDEDATA-Struktur nicht festgelegt ist (d. h. gleich 0), hat der Client angefordert, dass der Server das Datenelement jedes Mal sendet, wenn sich der Wert des Elements ändert. In solchen Fällen rendert der Server den neuen Wert des Datenelements im zuvor angegebenen Format und sendet dem Client eine WM _ DDE _ DATA-Nachricht, wie im folgenden Beispiel gezeigt.
// 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.
}
In diesem Beispiel verarbeitet der Client den Elementwert entsprechend. Wenn das fAckReq-Flag für das Element festgelegt ist, sendet der Client dem Server eine positive WM _ DDE _ ACK-Nachricht.
Wenn der Client den Link mit dem fDeferUpd-Membersatz (d. h. gleich 1) ein richtet, hat der Client angefordert, dass bei jeder Änderung der Daten nur eine Benachrichtigung gesendet wird, nicht die Daten selbst. In solchen Fällen rendert der Server den Wert nicht, wenn sich der Elementwert ändert, sondern sendet dem Client einfach eine WM _ DDE _ DATA-Nachricht mit einem NULL-Datenhand handle, wie im folgenden Beispiel veranschaulicht.
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.
}
Bei Bedarf kann der Client den aktuellen Wert des Datenelements anfordern, indem er eine normale WM _ DDE _ REQUEST-Nachricht ausstellen oder einfach den Hinweis vom Server ignorieren kann, dass sich die Daten geändert haben. Wenn fAckReq gleich 1 ist, wird vom Client erwartet, dass er eine positive WM _ DDE _ ACK-Nachricht an den Server sendet.
Beenden eines Datenlinks
Wenn der Client an fordert, dass ein bestimmter Datenlink beendet wird, sendet der Client dem Server eine WM _ DDE _ UNADVISE-Nachricht, wie im folgenden Beispiel gezeigt.
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.
}
Der Server überprüft, ob der Client derzeit über einen Link zum jeweiligen Element in dieser Konversation verfügt. Wenn ein Link vorhanden ist, sendet der Server dem Client eine positive WM _ DDE _ ACK-Nachricht. Der Server muss dann keine Updates zum Element mehr senden. Wenn kein Link vorhanden ist, sendet der Server dem Client eine negative WM _ DDE _ ACK-Nachricht.
Die WM _ DDE _ UNADVISE-Meldung gibt ein Datenformat an. Das Format 0 (null) informiert den Server darüber, alle Links für das angegebene Element zu beenden, auch wenn mehrere hot-Links eingerichtet sind und jede ein anderes Format verwendet.
Um alle Links für eine Konversation zu beenden, sendet die Clientanwendung dem Server eine WM _ DDE _ UNADVISE-Nachricht mit einem NULL-Elementatom. Der Server bestimmt, ob für die Konversation mindestens ein Link eingerichtet ist. Wenn ein Link vorhanden ist, sendet der Server dem Client eine positive WM _ DDE _ ACK-Nachricht. Der Server muss dann keine Updates mehr in der Konversation senden. Wenn kein Link vorhanden ist, sendet der Server dem Client eine negative WM _ DDE _ ACK-Nachricht.
Ausführen von Befehlen in einer Serveranwendung
Anwendungen können die WM _ DDE _ EXECUTE-Nachricht verwenden, um zu bewirken, dass ein bestimmter Befehl oder eine Reihe von Befehlen in einer anderen Anwendung ausgeführt wird. Hierzu sendet der Client dem Server eine WM _ DDE _ EXECUTE-Nachricht mit einem Handle an eine Befehlszeichenfolge, wie im folgenden Beispiel gezeigt.
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);
}
In diesem Beispiel versucht der Server, die angegebene Befehlszeichenfolge auszuführen. Wenn dies erfolgreich ist, sendet der Server dem Client eine positive WM _ DDE _ ACK-Nachricht. Andernfalls sendet er eine negative WM _ DDE _ ACK-Nachricht. In dieser WM _ DDE _ ACK-Nachricht wird das hCommand-Handle wiederverwendet, das in der ursprünglichen WM _ DDE _ EXECUTE-Nachricht übergeben wurde.
Wenn die Befehlsausführungszeichenfolge des Clients angibt, dass der Server beendet wird, sollte der Server antworten, indem er eine positive WM _ DDE _ ACK-Nachricht sendet und dann vor dem Beenden eine WM _ DDE _ TERMINATE-Nachricht sendet. Alle anderen Befehle, die mit einer WM _ DDE _ EXECUTE-Nachricht gesendet werden, sollten synchron ausgeführt werden. Das heißt, der Server sollte eine WM _ DDE _ ACK-Nachricht erst senden, nachdem der Befehl erfolgreich abgeschlossen wurde.
Beenden einer Konversation
Entweder der Client oder der Server kann eine WM _ DDE _ TERMINATE-Nachricht aus geben, um eine Konversation jederzeit zu beenden. Ebenso sollten sowohl die Client- als auch die Serveranwendungen darauf vorbereitet sein, diese Nachricht jederzeit zu empfangen. Eine Anwendung muss alle Konversationen beenden, bevor sie heruntergefahren wird.
Im folgenden Beispiel veröffentlicht die Anwendung, die die Konversation beendet, eine WM _ DDE _ TERMINATE-Nachricht.
PostMessage(hwndServerDDE, WM_DDE_TERMINATE,
(WPARAM) hwndClientDDE, 0);
Dadurch wird die andere Anwendung darüber informiert, dass die sendende Anwendung keine weiteren Nachrichten sendet und der Empfänger sein Fenster schließen kann. Es wird erwartet, dass der Empfänger in allen Fällen sofort antwortet, indem er eine WM _ DDE _ TERMINATE-Nachricht sendet. Der Empfänger darf keine negative, ausgelastete oder positive WM _ DDE _ ACK-Nachricht senden.
Nachdem eine Anwendung die WM _ DDE _ TERMINATE-Nachricht an den Partner in einer DDE-Konversation gesendet hat, darf sie nicht auf Nachrichten von diesem Partner reagieren, da der Partner möglicherweise das Fenster zerstört hat, an das die Antwort gesendet wird.
Wenn eine Anwendung eine andere DDE-Nachricht als WM DDE TERMINATE empfängt, nachdem sie WM _ DDE _ TERMINATE gepostet hat, sollte sie alle Objekte, die den empfangenen Nachrichten zugeordnet sind, mit Ausnahme der Datenhandles für WM _ DDE _ DATA- oder WM _ _ DDE _ _ POKE-Nachrichten, für die kein fRelease-Member festgelegt ist, frei geben.
Wenn eine Anwendung beendet wird, sollten alle aktiven DDE-Konversationen beendet werden, bevor die Verarbeitung der WM _ DESTROY-Nachricht abgeschlossen wird. Wenn eine Anwendung ihre aktiven DDE-Konversationen jedoch nicht beendet, beendet das System alle DDE-Konversationen, die einem Fenster zugeordnet sind, wenn das Fenster zerstört wird. Das folgende Beispiel zeigt, wie eine Serveranwendung alle DDE-Konversationen beendet.
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;
}