Uso de Exchange de datos dinámicos
Esta sección tiene ejemplos de código en las siguientes tareas:
- Iniciar una conversación
- Transferencia de un solo elemento
- Establecimiento de un vínculo de datos permanente
- Llevar a cabo comandos en una aplicación de servidor
- Terminación de una conversación
Iniciar una conversación
Para iniciar una conversación de Exchange de datos dinámicos (DDE), el cliente envía un mensaje WM_DDE_INITIATE. Normalmente, el cliente difunde este mensaje llamando a SendMessage, con –1 como primer parámetro. Si la aplicación ya tiene el identificador de ventana de la aplicación de servidor, puede enviar el mensaje directamente a esa ventana. El cliente prepara átomos para el nombre de la aplicación y el nombre del tema mediante una llamada a GlobalAddAtom. El cliente puede solicitar conversaciones con cualquier aplicación de servidor potencial y para cualquier tema potencial proporcionando átomos NULL (comodín) para la aplicación y el tema.
En el ejemplo siguiente se muestra cómo el cliente inicia una conversación, donde se especifican la aplicación y el tema.
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);
Nota
Si la aplicación usa átomos NULL , no es necesario usar las funciones GlobalAddAtom y GlobalDeleteAtom . En este ejemplo, la aplicación cliente crea dos átomos globales que contienen el nombre del servidor y el nombre del tema, respectivamente.
La aplicación cliente envía un mensaje de WM_DDE_INITIATE con estos dos átomos en el parámetro lParam del mensaje. En la llamada a la función SendMessage , el identificador de ventana especial –1 dirige el sistema para enviar este mensaje a todas las demás aplicaciones activas. SendMessage no vuelve a la aplicación cliente hasta que todas las aplicaciones que reciben el mensaje tienen, a su vez, el control devuelto al sistema. Esto significa que se garantiza que el cliente haya procesado todos los mensajes WM_DDE_ACK enviados en respuesta por las aplicaciones de servidor cuando se haya devuelto la llamada SendMessage .
Después de que SendMessage vuelva, la aplicación cliente elimina los átomos globales.
Las aplicaciones de servidor responden según la lógica que se muestra en el diagrama siguiente.
Para confirmar uno o varios temas, el servidor debe crear átomos para cada conversación (lo que requiere átomos duplicados de nombre de aplicación si hay varios temas) y enviar un mensaje de WM_DDE_ACK para cada conversación, como se muestra en el ejemplo siguiente.
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.
}
Cuando un servidor responde con un mensaje de WM_DDE_ACK , la aplicación cliente debe guardar un identificador en la ventana del servidor. El cliente que recibe el identificador como el parámetro wParam del mensaje de WM_DDE_ACK envía todos los mensajes DDE posteriores a la ventana del servidor que identifica este identificador.
Si la aplicación cliente usa un átomo NULL para el nombre de la aplicación o el nombre del tema, espere que la aplicación reciba confirmaciones de más de una aplicación de servidor. Varias confirmaciones también pueden provenir de varias instancias de un servidor DDE, incluso si la aplicación cliente no usa átomos NULL . Un servidor siempre debe usar una ventana única para cada conversación. El procedimiento de ventana de la aplicación cliente puede usar un identificador para la ventana del servidor (proporcionado como el parámetro lParam de WM_DDE_INITIATE) para realizar un seguimiento de varias conversaciones. Esto permite que una sola ventana de cliente procese varias conversaciones sin necesidad de finalizar y volver a conectarse con una nueva ventana de cliente para cada conversación.
Transferencia de un solo elemento
Una vez establecida una conversación DDE, el cliente puede recuperar el valor de un elemento de datos del servidor emitiendo el mensaje de WM_DDE_REQUEST o enviando un valor de elemento de datos al servidor emitiendo WM_DDE_POKE.
Recuperar un elemento del servidor
Para recuperar un elemento del servidor, el cliente envía al servidor un mensaje de WM_DDE_REQUEST que especifica el elemento y el formato que se van a recuperar, como se muestra en el ejemplo siguiente.
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.
}
En este ejemplo, el cliente especifica el formato del Portapapeles CF_TEXT como formato preferido para el elemento de datos solicitado.
El receptor (servidor) del mensaje WM_DDE_REQUEST normalmente debe eliminar el átomo de elemento, pero si se produce un error en la llamada PostMessage , el cliente debe eliminar el átomo.
Si el servidor tiene acceso al elemento solicitado y puede representarlo en el formato solicitado, el servidor copia el valor del elemento como un objeto de memoria compartida y envía al cliente un mensaje de WM_DDE_DATA , como se muestra en el ejemplo siguiente.
// 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.
}
En este ejemplo, la aplicación de servidor asigna un objeto de memoria para que contenga el elemento de datos. El objeto de datos se inicializa como una estructura DDEDATA .
A continuación, la aplicación de servidor establece el miembro cfFormat de la estructura en CF_TEXT para informar a la aplicación cliente de que los datos están en formato de texto. El cliente responde copiando el valor de los datos solicitados en el miembro Value de la estructura DDEDATA . Una vez que el servidor ha rellenado el objeto de datos, el servidor desbloquea los datos y crea un átomo global que contiene el nombre del elemento de datos.
Por último, el servidor emite el mensaje de WM_DDE_DATA llamando a PostMessage. El identificador del objeto de datos y el átomo que contiene el nombre del elemento se empaquetan en el parámetro lParam del mensaje mediante la función PackDDElParam .
Si se produce un error en PostMessage , el servidor debe usar la función FreeDDElParam para liberar el parámetro lParam empaquetado. El servidor también debe liberar el parámetro lParam empaquetado para el WM_DDE_REQUEST mensaje que recibió.
Si el servidor no puede satisfacer la solicitud, envía un mensaje de WM_DDE_ACK negativo al cliente, como se muestra en el ejemplo siguiente.
// Negative acknowledgment.
PostMessage(hwndClientDDE,
WM_DDE_ACK,
(WPARAM) hwndServerDDE,
PackDDElParam(WM_DDE_ACK, 0, atomItem));
Al recibir un mensaje de WM_DDE_DATA , el cliente procesa el valor del elemento de datos según corresponda. A continuación, si el miembro fAckReq al que apunta en el mensaje WM_DDE_DATA es 1, el cliente debe enviar al servidor un mensaje de WM_DDE_ACK positivo, como se muestra en el ejemplo siguiente.
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);
En este ejemplo, el cliente examina el formato de los datos. Si el formato no es CF_TEXT (o si el cliente no puede bloquear la memoria de los datos), el cliente envía un mensaje de WM_DDE_ACK negativo para indicar que no puede procesar los datos. Si el cliente no puede bloquear un identificador de datos porque el identificador contiene el miembro fAckReq , el cliente no debe enviar un mensaje de WM_DDE_ACK negativo. En su lugar, el cliente debe finalizar la conversación.
Si un cliente envía una confirmación negativa en respuesta a un mensaje de WM_DDE_DATA , el servidor es responsable de liberar la memoria (pero no el parámetro lParam ) al que hace referencia el mensaje WM_DDE_DATA asociado a la confirmación negativa.
Si puede procesar los datos, el cliente examina el miembro fAckReq de la estructura DDEDATA para determinar si el servidor solicitó que se le informara de que el cliente recibió y procesó los datos correctamente. Si el servidor solicitó esta información, el cliente envía al servidor un mensaje positivo WM_DDE_ACK .
Dado que el desbloqueo de datos invalida el puntero a los datos, el cliente guarda el valor del miembro fRelease antes de desbloquear el objeto de datos. Después de guardar el valor, el cliente lo examina para determinar si la aplicación de servidor solicitó al cliente liberar la memoria que contiene los datos; el cliente actúa en consecuencia.
Al recibir un mensaje de WM_DDE_ACK negativo, el cliente puede solicitar de nuevo el mismo valor de elemento, especificando un formato de Portapapeles diferente. Normalmente, un cliente pedirá primero el formato más complejo que puede admitir y, a continuación, desactive si es necesario a través de formatos progresivamente más sencillos hasta que encuentre uno que pueda proporcionar el servidor.
Si el servidor admite el elemento Formats del tema del sistema, el cliente puede determinar una vez que el portapapeles da formato al servidor, en lugar de determinarlos cada vez que el cliente solicita un elemento.
Envío de un elemento al servidor
El cliente puede enviar un valor de elemento al servidor mediante el mensaje WM_DDE_POKE . El cliente representa el elemento que se va a enviar y envía el mensaje WM_DDE_POKE , como se muestra en el ejemplo siguiente.
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.
}
Nota
El envío de datos mediante un mensaje de WM_DDE_POKE es esencialmente el mismo que el envío mediante WM_DDE_DATA, salvo que WM_DDE_POKE se envía desde el cliente al servidor.
Si el servidor puede aceptar el valor del elemento de datos en el formato representado por el cliente, el servidor procesa el valor del elemento según corresponda y envía al cliente un mensaje positivo WM_DDE_ACK . Si no puede procesar el valor del elemento, debido a su formato o por otros motivos, el servidor envía al cliente un mensaje de WM_DDE_ACK negativo.
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.
En este ejemplo, el servidor llama a GlobalGetAtomName para recuperar el nombre del elemento que envió el cliente. A continuación, el servidor determina si admite el elemento y si el elemento se representa en el formato correcto (es decir, CF_TEXT). Si el elemento no se admite y no se representa en el formato correcto, o si el servidor no puede bloquear la memoria de los datos, el servidor envía una confirmación negativa a la aplicación cliente. Tenga en cuenta que, en este caso, enviar una confirmación negativa es correcta porque siempre se supone que WM_DDE_POKE mensajes tienen establecido el miembro fAckReq . El servidor debe omitir el miembro.
Si un servidor envía una confirmación negativa en respuesta a un mensaje de WM_DDE_POKE , el cliente es responsable de liberar la memoria (pero no el parámetro lParam ) al que hace referencia el mensaje WM_DDE_POKE asociado a la confirmación negativa.
Establecimiento de un vínculo de datos permanente
Una aplicación cliente puede usar DDE para establecer un vínculo a un elemento de una aplicación de servidor. Una vez establecido este vínculo, el servidor envía actualizaciones periódicas del elemento vinculado al cliente, normalmente, siempre que cambie el valor del elemento. Por lo tanto, se establece un flujo de datos permanente entre las dos aplicaciones; este flujo de datos permanece en su lugar hasta que se desconecta explícitamente.
Iniciar un vínculo de datos
El cliente inicia un vínculo de datos publicando un mensaje de WM_DDE_ADVISE , como se muestra en el ejemplo siguiente.
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
}
En este ejemplo, la aplicación cliente establece la marca fDeferUpd del mensaje de WM_DDE_ADVISE en FALSE. Esto dirige a la aplicación de servidor para enviar los datos al cliente cada vez que cambian los datos.
Si el servidor no puede atender la solicitud de WM_DDE_ADVISE , envía al cliente un mensaje negativo WM_DDE_ACK . Pero si el servidor tiene acceso al elemento y puede representarlo en el formato solicitado, el servidor anota el nuevo vínculo (recuperando las marcas especificadas en el parámetro hOptions ) y envía al cliente un mensaje positivo WM_DDE_ACK . Desde entonces, hasta que el cliente emite un mensaje de WM_DDE_UNADVISE coincidente, el servidor envía los nuevos datos al cliente cada vez que cambia el valor del elemento en el servidor.
El mensaje WM_DDE_ADVISE establece el formato de los datos que se intercambiarán durante el vínculo. Si el cliente intenta establecer otro vínculo con el mismo elemento, pero usa un formato de datos diferente, el servidor puede optar por rechazar el segundo formato de datos o intentar admitirlo. Si se ha establecido un vínculo intermedio para cualquier elemento de datos, el servidor solo puede admitir un formato de datos a la vez. Esto se debe a que el mensaje de WM_DDE_DATA para un vínculo intermedio tiene un identificador de datos NULL , que de lo contrario contiene la información de formato. Por lo tanto, un servidor debe rechazar todos los vínculos intermedios de un elemento ya vinculado y debe rechazar todos los vínculos de un elemento que tenga vínculos intermedios. Otra interpretación puede ser que el servidor cambie el formato y el estado frecuente o intermedio de un vínculo cuando se solicite un segundo vínculo para el mismo elemento de datos.
En general, las aplicaciones cliente no deben intentar establecer más de un vínculo a la vez para un elemento de datos.
Iniciar un vínculo de datos con el comando Pegar vínculo
Las aplicaciones que admiten vínculos de datos activos o intermedios normalmente admiten un formato de Portapapeles registrado denominado Link. Cuando se asocia con los comandos Copiar y pegar vínculo de la aplicación, este formato de Portapapeles permite al usuario establecer conversaciones de DDE entre aplicaciones simplemente copiando un elemento de datos en la aplicación de servidor y pegandolo en la aplicación cliente.
Una aplicación de servidor admite el formato del Portapapeles de vínculo colocando en el Portapapeles una cadena que contiene los nombres de aplicación, tema y elemento cuando el usuario elige el comando Copiar en el menú Editar . A continuación se muestra el formato de vínculo estándar:
application**\0topic\0item\0\0**
Un solo carácter null separa los nombres y dos caracteres NULL finalizan toda la cadena.
Las aplicaciones cliente y servidor deben registrar el formato del Portapapeles de vínculo, como se muestra a continuación:
cfLink = RegisterClipboardFormat("Link");
Una aplicación cliente admite el formato del Portapapeles link mediante un comando Pegar vínculo en su menú Editar. Cuando el usuario elige este comando, la aplicación cliente analiza la aplicación, el tema y los nombres de elemento de los datos del Portapapeles de formato de vínculo. Con estos nombres, la aplicación cliente inicia una conversación para la aplicación y el tema, si dicha conversación aún no existe. A continuación, la aplicación cliente envía un mensaje WM_DDE_ADVISE a la aplicación de servidor, especificando el nombre del elemento contenido en los datos del Portapapeles de formato de vínculo.
A continuación se muestra un ejemplo de la respuesta de una aplicación cliente cuando el usuario elige el comando Pegar vínculo.
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;
}
En este ejemplo, la aplicación cliente abre el Portapapeles y determina si contiene datos en el formato Link (es decir, cfLink) que había registrado anteriormente. Si no es así, o si no puede bloquear los datos en el Portapapeles, el cliente devuelve.
Una vez que la aplicación cliente recupera un puntero a los datos del Portapapeles, analiza los datos para extraer los nombres de aplicación, tema y elemento.
La aplicación cliente determina si ya existe una conversación en el tema entre ella y la aplicación de servidor. Si existe una conversación, el cliente comprueba si ya existe un vínculo para el elemento de datos. Si existe este vínculo, el cliente muestra un cuadro de mensaje al usuario; de lo contrario, llama a su propia función SendAdvise para enviar un mensaje WM_DDE_ADVISE al servidor para el elemento.
Si aún no existe una conversación en el tema entre el cliente y el servidor, el cliente llama primero a su propia función SendInitiate para difundir el mensaje de WM_DDE_INITIATE para solicitar una conversación y, en segundo lugar, llama a su propia función FindServerGivenAppTopic para establecer la conversación con la ventana que responde en nombre de la aplicación de servidor. Una vez iniciada la conversación, la aplicación cliente llama a SendAdvise para solicitar el vínculo.
Notificación al cliente de que los datos han cambiado
Cuando el cliente establece un vínculo mediante el mensaje de WM_DDE_ADVISE , con el miembro fDeferUpd no establecido (es decir, igual a cero) en la estructura DDEDATA , el cliente ha solicitado al servidor enviar el elemento de datos cada vez que cambia el valor del elemento. En tales casos, el servidor representa el nuevo valor del elemento de datos en el formato especificado anteriormente y envía al cliente un mensaje de WM_DDE_DATA , como se muestra en el ejemplo siguiente.
// 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.
}
En este ejemplo, el cliente procesa el valor del elemento según corresponda. Si se establece la marca fAckReq del elemento, el cliente envía al servidor un mensaje positivo WM_DDE_ACK .
Cuando el cliente establece el vínculo, con el conjunto de miembros fDeferUpd (es decir, igual a 1), el cliente ha solicitado que solo se envíe una notificación, no los propios datos, cada vez que cambien los datos. En tales casos, cuando cambia el valor del elemento, el servidor no representa el valor, sino simplemente envía al cliente un mensaje WM_DDE_DATA con un identificador de datos NULL, como se muestra en el ejemplo siguiente.
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.
}
Según sea necesario, el cliente puede solicitar el valor más reciente del elemento de datos emitiendo un mensaje de WM_DDE_REQUEST normal, o simplemente puede omitir el aviso del servidor que los datos han cambiado. En cualquier caso, si fAckReq es igual a 1, se espera que el cliente envíe un mensaje positivo WM_DDE_ACK al servidor.
Terminación de un vínculo de datos
Si el cliente solicita que finalice un vínculo de datos específico, el cliente envía al servidor un mensaje de WM_DDE_UNADVISE , como se muestra en el ejemplo siguiente.
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.
}
El servidor comprueba si el cliente tiene actualmente un vínculo al elemento específico de esta conversación. Si existe un vínculo, el servidor envía al cliente un mensaje positivo WM_DDE_ACK ; El servidor ya no es necesario para enviar actualizaciones sobre el elemento. Si no existe ningún vínculo, el servidor envía al cliente un mensaje negativo WM_DDE_ACK .
El mensaje WM_DDE_UNADVISE especifica un formato de datos. Un formato de cero informa al servidor para detener todos los vínculos del elemento especificado, incluso si se establecen varios vínculos activos y cada uno usa un formato diferente.
Para finalizar todos los vínculos de una conversación, la aplicación cliente envía al servidor un mensaje de WM_DDE_UNADVISE con un átomo de elemento nulo. El servidor determina si la conversación tiene al menos un vínculo establecido actualmente. Si existe un vínculo, el servidor envía al cliente un mensaje positivo WM_DDE_ACK ; El servidor ya no tiene que enviar ninguna actualización en la conversación. Si no existe ningún vínculo, el servidor envía al cliente un mensaje negativo WM_DDE_ACK .
Llevar a cabo comandos en una aplicación de servidor
Las aplicaciones pueden usar el mensaje WM_DDE_EXECUTE para hacer que se lleve a cabo un determinado comando o serie de comandos en otra aplicación. Para ello, el cliente envía al servidor un mensaje de WM_DDE_EXECUTE que contiene un identificador a una cadena de comandos, como se muestra en el ejemplo siguiente.
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);
}
En este ejemplo, el servidor intenta llevar a cabo la cadena de comandos especificada. Si se ejecuta correctamente, el servidor envía al cliente un mensaje positivo WM_DDE_ACK ; de lo contrario, envía un mensaje de WM_DDE_ACK negativo. Este WM_DDE_ACK mensaje reutiliza el identificador hCommand pasado en el mensaje de WM_DDE_EXECUTE original.
Si la cadena de ejecución de comandos del cliente solicita que finalice el servidor, el servidor debe responder enviando un mensaje de WM_DDE_ACK positivo y, a continuación, publicar un mensaje de WM_DDE_TERMINATE antes de finalizar. Todos los demás comandos enviados con un mensaje de WM_DDE_EXECUTE deben ejecutarse sincrónicamente; es decir, el servidor debe enviar un mensaje de WM_DDE_ACK solo después de completar correctamente el comando.
Terminación de una conversación
El cliente o el servidor pueden emitir un mensaje de WM_DDE_TERMINATE para finalizar una conversación en cualquier momento. De forma similar, las aplicaciones cliente y servidor deben estar preparadas para recibir este mensaje en cualquier momento. Una aplicación debe finalizar todas sus conversaciones antes de apagarse.
En el ejemplo siguiente, la aplicación que termina la conversación publica un mensaje de WM_DDE_TERMINATE .
PostMessage(hwndServerDDE, WM_DDE_TERMINATE,
(WPARAM) hwndClientDDE, 0);
Esto informa a la otra aplicación de que la aplicación de envío no enviará más mensajes y el destinatario puede cerrar su ventana. Se espera que el destinatario responda rápidamente enviando un mensaje de WM_DDE_TERMINATE . El destinatario no debe enviar un mensaje negativo, ocupado o positivo WM_DDE_ACK .
Después de que una aplicación haya enviado el mensaje WM_DDE_TERMINATE al asociado en una conversación DDE, no debe responder a los mensajes de ese asociado, ya que es posible que el asociado haya destruido la ventana a la que se enviará la respuesta.
Si una aplicación recibe un mensaje DDE distinto de WM_DDE_TERMINATE después de haber publicado WM_DDE_TERMINATE, debe liberar todos los objetos asociados a los mensajes recibidos, excepto los identificadores de datos para los mensajes de WM_DDE_DATA o WM_DDE_POKE que no tienen establecido el miembro fRelease .
Cuando una aplicación está a punto de finalizar, debe finalizar todas las conversaciones de DDE activas antes de completar el procesamiento del mensaje de WM_DESTROY . Sin embargo, si una aplicación no finaliza sus conversaciones activas de DDE, el sistema finalizará las conversaciones de DDE asociadas a una ventana cuando se destruya la ventana. En el ejemplo siguiente se muestra cómo una aplicación de servidor finaliza todas las conversaciones de 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;
}