使用动态数据交换
本部分包含有关以下任务的代码示例:
启动对话
若要启动动态数据交换 (DDE) 对话,客户端应发送 WM_DDE_INITIATE 消息。 通常,客户端会调用 SendMessage 来广播此消息,其中将 –1 用作第一个参数。 如果应用程序已有服务器应用程序的窗口句柄,则可以将消息直接发送给该窗口。 客户端会调用 GlobalAddAtom 来为应用程序名称和主题名称准备原子。 客户端可以为应用程序和主题提供 NULL(通配符)原子,以请求与任何潜在服务器应用程序以及任何潜在主题的对话。
以下示例演示客户端如何启动对话,其中指定了应用程序和主题。
static BOOL fInInitiate = FALSE;
char *szApplication;
char *szTopic;
atomApplication = *szApplication == 0 ?
NULL : GlobalAddAtom((LPSTR) szApplication);
atomTopic = *szTopic == 0 ?
NULL : GlobalAddAtom((LPSTR) szTopic);
fInInitiate = TRUE;
SendMessage((HWND) HWND_BROADCAST, // broadcasts message
WM_DDE_INITIATE, // initiates conversation
(WPARAM) hwndClientDDE, // handle to client DDE window
MAKELONG(atomApplication, // application-name atom
atomTopic)); // topic-name atom
fInInitiate = FALSE;
if (atomApplication != NULL)
GlobalDeleteAtom(atomApplication);
if (atomTopic != NULL)
GlobalDeleteAtom(atomTopic);
注意
如果应用程序使用 NULL 原子,则无需使用 GlobalAddAtom 和 GlobalDeleteAtom 函数。 在此示例中,客户端应用程序创建了两个全局原子,分别包含服务器名称和主题名称。
客户端应用程序会在消息的 lParam 参数中,发送一条含这两个原子的 WM_DDE_INITIATE 消息。 在调用 SendMessage 函数时,特殊窗口句柄 –1 将指示系统将此消息发送给所有其他活动应用程序。 在接收消息的所有应用程序又将控制权返回给系统之前,SendMessage 不会返回到客户端应用程序。 这意味着,客户端保证会在 SendMessage 调用返回之前,处理完所有服务器应用程序在回复中发送的所有 WM_DDE_ACK 消息。
SendMessage 返回后,客户端应用程序将删除全局原子。
服务器应用程序根据下图所示的逻辑做出响应。
若要确认一个或多个主题,服务器必须为每个对话创建原子(如果有多个主题,则需要重复的应用程序名称原子),并为每个对话发送 WM_DDE_ACK 消息,如以下示例所示。
if ((atomApplication = GlobalAddAtom("Server")) != 0)
{
if ((atomTopic = GlobalAddAtom(szTopic)) != 0)
{
SendMessage(hwndClientDDE,
WM_DDE_ACK,
(WPARAM) hwndServerDDE,
MAKELONG(atomApplication, atomTopic));
GlobalDeleteAtom(atomTopic);
}
GlobalDeleteAtom(atomApplication);
}
if ((atomApplication == 0) || (atomTopic == 0))
{
// Handle errors.
}
当服务器以 WM_DDE_ACK 消息响应时,客户端应用程序应保存服务器窗口的句柄。 客户端会接收该句柄作为 WM_DDE_ACK 消息的 wParam 参数,然后会将所有后续 DDE 消息发送到此句柄标识的服务器窗口。
如果客户端应用程序对应用程序名称或主题名称使用 NULL 原子,则应用程序应会收到多个服务器应用程序的确认。 多个确认也可能来自 DDE 服务器的多个实例,即使客户端应用程序不使用 NULL 原子也是如此。 服务器应始终对每个对话使用唯一窗口。 客户端应用程序中的窗口过程可以使用服务器窗口的句柄(作为 WM_DDE_INITIATE 的 lParam 参数提供),以便跟踪多个对话。 这样,单个客户端窗口就可以处理多个对话,而无需终止并重新连接每个对话的新客户端窗口。
传输单项
建立 DDE 对话后,客户端可以发出 WM_DDE_REQUEST 消息来从服务器检索数据项的值,也可以发出 WM_DDE_POKE 来将数据项值提交到服务器。
从服务器检索项
若要从服务器检索项,客户端应向服务器发送一条 WM_DDE_REQUEST 消息,以指定要检索的项和格式,如以下示例所示。
if ((atomItem = GlobalAddAtom(szItemName)) != 0)
{
if (!PostMessage(hwndServerDDE,
WM_DDE_REQUEST,
(WPARAM) hwndClientDDE,
PackDDElParam(WM_DDE_REQUEST, CF_TEXT, atomItem)))
{
GlobalDeleteAtom(atomItem);
}
}
if (atomItem == 0)
{
// Handle errors.
}
在此示例中,客户端将剪贴板格式 CF_TEXT 指定为所请求数据项的首选格式。
WM_DDE_REQUEST 消息的接收方(服务器)通常必须删除项原子,但如果 PostMessage 调用失败,则客户端必须删除原子。
如果服务器有权访问所请求项,并且可以采用请求的格式呈现该项,则服务器会将项值复制为共享内存对象,并向客户端发送一条 WM_DDE_DATA 消息,如以下示例所示。
// Allocate the size of the DDE data header, plus the data: a
// string,<CR><LF><NULL>. The byte for the string's terminating
// null character is counted by DDEDATA.Value[1].
size_t* pcch;
HRESULT hResult;
hResult = StringCchLength(szItemValue,STRSAFE_MAX_CCH, pcch);
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
if (!(hData = GlobalAlloc(GMEM_MOVEABLE,
(LONG) sizeof(DDEDATA) + *pcch + 2)))
{
return;
}
if (!(lpData = (DDEDATA FAR*) GlobalLock(hData)))
{
GlobalFree(hData);
return;
}
lpData->cfFormat = CF_TEXT;
hResult = StringCchCopy((LPSTR) lpData->Value, *pcch +1, (LPCSTR) szItemValue); // copies value to be sent
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
// Each line of CF_TEXT data is terminated by CR/LF.
hResult = StringCchCat((LPSTR) lpData->Value, *pcch + 3, (LPCSTR) "\r\n");
if (FAILED(hResult)
{
// TODO: Write error handler.
return;
}
GlobalUnlock(hData);
if ((atomItem = GlobalAddAtom((LPSTR) szItemName)) != 0)
{
lParam = PackDDElParam(WM_DDE_ACK, (UINT) hData, atomItem);
if (!PostMessage(hwndClientDDE,
WM_DDE_DATA,
(WPARAM) hwndServerDDE,
lParam))
{
GlobalFree(hData);
GlobalDeleteAtom(atomItem);
FreeDDElParam(WM_DDE_ACK, lParam);
}
}
if (atomItem == 0)
{
// Handle errors.
}
在此示例中,服务器应用程序会分配一个内存对象,以包含数据项。 数据对象将初始化为 DDEDATA 结构。
然后,服务器应用程序将结构的 cfFormat 成员设置为 CF_TEXT,以通知客户端应用程序数据采用文本格式。 作为响应,客户端会将所请求数据的值复制到 DDEDATA 结构的 Value 成员。 在服务器填充数据对象后,服务器将解锁数据,并创建包含数据项名称的全局原子。
最后,服务器调用 PostMessage 来发出 WM_DDE_DATA 消息。 数据对象的句柄和包含项名称的原子将由 PackDDElParam 函数打包为消息的 lParam 参数。
如果 PostMessage 失败,服务器必须使用 FreeDDElParam 函数释放打包的 lParam 参数。 服务器还必须为收到的 WM_DDE_REQUEST 消息释放打包的 lParam 参数。
如果服务器无法满足请求,则会向客户端发送负 WM_DDE_ACK 消息,如以下示例所示。
// Negative acknowledgment.
PostMessage(hwndClientDDE,
WM_DDE_ACK,
(WPARAM) hwndServerDDE,
PackDDElParam(WM_DDE_ACK, 0, atomItem));
收到 WM_DDE_DATA 消息后,客户端会根据情况处理数据项值。 然后,如果 WM_DDE_DATA 消息中指向的 fAckReq 成员为 1,则客户端必须向服务器发送正 WM_DDE_ACK 消息,如以下示例所示。
UnpackDDElParam(WM_DDE_DATA, lParam, (PUINT) &hData,
(PUINT) &atomItem);
if (!(lpDDEData = (DDEDATA FAR*) GlobalLock(hData))
|| (lpDDEData->cfFormat != CF_TEXT))
{
PostMessage(hwndServerDDE,
WM_DDE_ACK,
(WPARAM) hwndClientDDE,
PackDDElParam(WM_DDE_ACK, 0, atomItem)); // Negative ACK.
}
// Copy data from lpDDEData here.
if (lpDDEData->fAckReq)
{
PostMessage(hwndServerDDE,
WM_DDE_ACK,
(WPARAM) hwndClientDDE,
PackDDElParam(WM_DDE_ACK, 0x8000,
atomItem)); // Positive ACK
}
bRelease = lpDDEData->fRelease;
GlobalUnlock(hData);
if (bRelease)
GlobalFree(hData);
在此示例中,客户端会检查数据的格式。 如果格式不是 CF_TEXT(或者客户端无法锁定数据的内存),客户端将发送负 WM_DDE_ACK 消息,以指示无法处理数据。 如果由于句柄包含 fAckReq 成员,客户端无法锁定数据句柄,则客户端不能发送负 WM_DDE_ACK 消息。 相反,客户端应终止对话。
如果客户端发送负确认来响应 WM_DDE_DATA 消息,则服务器负责释放与负确认关联的 WM_DDE_DATA 消息引用的内存(但不释放 lParam 参数)。
如果客户端可以处理数据,则客户端会检查 DDEDATA 结构的 fAckReq 成员,以确定服务器是否请求了被告知客户端已成功收到和处理数据。 如果服务器确实请求了此信息,客户端会向服务器发送一条正 WM_DDE_ACK 消息。
由于解锁数据会使指向数据的指针失效,因此客户端会在解锁数据对象之前保存 fRelease 成员的值。 保存值后,客户端会检查该值,以确定服务器应用程序是否请求客户端释放包含数据的内存;客户端会相应地执行操作。
收到负 WM_DDE_ACK 消息后,客户端可以再次请求相同的项值,从而指定不同的剪贴板格式。 通常,客户端首先会请求可以支持的最复杂格式,然后在必要时逐步下降到更简单的格式,直到找到服务器可以提供的格式。
如果服务器支持系统主题的“格式”项,客户端就可以确定服务器支持的剪贴板格式,而不是在每次客户端请求项时确定。
将项提交到服务器
客户端可以使用 WM_DDE_POKE 消息将项值发送到服务器。 客户端呈现要发送的项,并发送 WM_DDE_POKE 消息,如以下示例所示。
size_t* pcch;
HRESULT hResult;
hResult = StringCchLength(szValue,STRSAFE_MAX_CCH, pcch);
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
if (!(hPokeData = GlobalAlloc(GMEM_MOVEABLE,
(LONG) sizeof(DDEPOKE) + *pcch + 2)))
{
return;
}
if (!(lpPokeData = (DDEPOKE *) GlobalLock(hPokeData)))
{
GlobalFree(hPokeData);
return;
}
lpPokeData->fRelease = TRUE;
lpPokeData->cfFormat = CF_TEXT;
hResult = StringCchCopy((LPSTR) lpPokeData->Value, *pcch +1, (LPCSTR) szValue); // copies value to be sent
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
// Each line of CF_TEXT data is terminated by CR/LF.
hResult = StringCchCat((LPSTR) lpPokeData->Value, *pcch + 3, (LPCSTR) "\r\n");
if (FAILED(hResult)
{
// TODO: Write error handler.
return;
}
GlobalUnlock(hPokeData);
if ((atomItem = GlobalAddAtom((LPSTR) szItem)) != 0)
{
if (!PostMessage(hwndServerDDE,
WM_DDE_POKE,
(WPARAM) hwndClientDDE,
PackDDElParam(WM_DDE_POKE, (UINT) hPokeData,
atomItem)))
{
GlobalDeleteAtom(atomItem);
GlobalFree(hPokeData);
}
}
if (atomItem == 0)
{
// Handle errors.
}
注意
使用 WM_DDE_POKE 消息发送数据实际上与使用 WM_DDE_DATA 发送数据相同,只不过 WM_DDE_POKE 是从客户端发送到服务器。
如果服务器能够接受采用客户端呈现的格式的数据项值,则服务器会根据情况处理项值,并向客户端发送正 WM_DDE_ACK 消息。 如果由于项值的格式或其他原因而无法处理项值,则服务器会向客户端发送负 WM_DDE_ACK 消息。
UnpackDDElParam(WM_DDE_POKE, lParam, (PUINT) &hPokeData,
(PUINT) &atomItem);
GlobalGetAtomName(atomItem, szItemName, ITEM_NAME_MAX_SIZE);
if (!(lpPokeData = (DDEPOKE *) GlobalLock(hPokeData))
|| lpPokeData->cfFormat != CF_TEXT
|| !IsItemSupportedByServer(szItemName))
{
PostMessage(hwndClientDDE,
WM_DDE_ACK,
(WPARAM) hwndServerDDE,
PackDDElParam(WM_DDE_ACK, 0, atomItem)); // negative ACK
}
hResult = StringCchLength(szItemValue,STRSAFE_MAX_CCH, pcch);
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
hResult = StringCchCopy(szItemValue, *pcch +1, lpPokeData->Value); // copies value
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
bRelease = lpPokeData->fRelease;
GlobalUnlock(hPokeData);
if (bRelease)
{
GlobalFree(hPokeData);
}
PostMessage(hwndClientDDE,
WM_DDE_ACK,
(WPARAM) hwndServerDDE,
PackDDElParam(WM_DDE_ACK,
0x8000, atomItem)); // positive ACK.
在此示例中,服务器会调用 GlobalGetAtomName 来检索客户端发送的项的名称。 然后,服务器确定是否支持该项,以及该项是否以正确的格式(即 CF_TEXT)呈现。 如果项不受支持且未以正确的格式呈现,或者服务器无法锁定数据的内存,则服务器会将负确认发送回客户端应用程序。 请注意,在这种情况下,发送负确认是正确的,因为始终假定 WM_DDE_POKE 消息设置了 fAckReq 成员。 服务器应忽略该成员。
如果服务发送负确认来响应 WM_DDE_POKE 消息,则客户端负责释放与负确认关联的 WM_DDE_POKE 消息引用的内存(但不释放 lParam 参数)。
建立永久数据链接
客户端应用程序可以使用 DDE 建立指向服务器应用程序中项的链接。 建立此类链接后,服务器会将链接项的定期更新发送到客户端,通常只要项的值发生更改就发送。 因此,会在两个应用程序之间建立永久数据流;在显式断开连接之前,此数据流会保持原位。
启动数据链接
客户端将发布 WM_DDE_ADVISE 消息来启动数据链接,如以下示例所示。
if (!(hOptions = GlobalAlloc(GMEM_MOVEABLE,
sizeof(DDEADVISE))))
return;
if (!(lpOptions = (DDEADVISE FAR*) GlobalLock(hOptions)))
{
GlobalFree(hOptions);
return;
}
lpOptions->cfFormat = CF_TEXT;
lpOptions->fAckReq = TRUE;
lpOptions->fDeferUpd = FALSE;
GlobalUnlock(hOptions);
if ((atomItem = GlobalAddAtom(szItemName)) != 0)
{
if (!(PostMessage(hwndServerDDE,
WM_DDE_ADVISE,
(WPARAM) hwndClientDDE,
PackDDElParam(WM_DDE_ADVISE, (UINT) hOptions,
atomItem))))
{
GlobalDeleteAtom(atomItem);
GlobalFree(hOptions);
FreeDDElParam(WM_DDE_ADVISE, lParam);
}
}
if (atomItem == 0)
{
// Handle errors
}
在此示例中,客户端应用程序将 WM_DDE_ADVISE 消息的 fDeferUpd 标志设置为 FALSE。 这会指示服务器应用程序在数据发生更改时将数据发送到客户端。
如果服务器无法满足 WM_DDE_ADVISE 请求,它会向客户端发送负 WM_DDE_ACK 消息。 但是,如果服务器有权访问该项,并且可以采用请求的格式呈现该项,则服务器会记下新链接(撤回在 hOptions 参数中指定的标志),并向客户端发送正 WM_DDE_ACK 消息。 此后,在客户端发出匹配的 WM_DDE_UNADVISE 消息之前,每次服务器中的项值发生更改时,服务器都会向客户端发送新数据。
WM_DDE_ADVISE 消息将建立链接期间要交换的数据的格式。 如果客户端尝试与同一项建立另一个链接,但使用的是不同的数据格式,则服务器可以选择拒绝第二种数据格式,或尝试支持该格式。 如果为任何数据项建立了暖链接,服务器一次只能支持一种数据格式。 这是因为暖链接的 WM_DDE_DATA 消息具有 NULL 数据句柄,否则包含格式信息。 因此,服务器必须拒绝已链接的项的所有暖链接,并且必须拒绝具有暖链接的项的所有链接。 另一种解释可能是,当请求同一数据项的第二个链接时,服务器将更改链接的格式和热状态或暖状态。
通常,客户端应用程序不能一次尝试为一个数据项建立多个链接。
使用“粘贴链接”命令启动数据链接
支持热或暖数据链接的应用程序通常支持名为“链接”的已注册剪贴板格式。 与应用程序的“复制链接”命令和“粘贴链接”命令关联时,此剪贴板格式将使用户只需在服务器应用程序中复制数据项并将其粘贴到客户端应用程序中,即可在应用程序之间建立 DDE 对话。
当用户从“编辑”菜单中选择“复制”命令时,服务器应用程序会在剪贴板中放置一个包含应用程序、主题和项名称的字符串,以支持“链接”剪贴板格式。 下面是标准“链接”格式:
application**\0topic\0item\0\0**
单个 null 字符可用于分隔名称,两个 null 字符可用于终止整个字符串。
客户端和服务器应用程序都必须注册“链接”剪贴板格式,具体如下:
cfLink = RegisterClipboardFormat("Link");
客户端应用程序通过其“编辑”菜单上的“粘贴链接”命令来支持“链接”剪贴板格式。 当用户选择此命令时,客户端应用程序会解析“链接”格式剪贴板数据中的应用程序、主题和项名称。 使用这些名称,客户端应用程序将启动应用程序和主题的对话(如果此类对话尚不存在)。 然后,客户端应用程序将 WM_DDE_ADVISE 消息发送到服务器应用程序,指定“链接”格式剪贴板数据中包含的项名称。
下面是用户选择“粘贴链接”命令时的客户端应用程序响应示例。
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;
}
在此示例中,客户端应用程序打开剪贴板,并确定是否包含采用之前注册过的“链接”格式(即 cfLink)的数据。 如果不包含,或者无法锁定剪贴板中的数据,客户端将返回。
客户端应用程序检索指向剪贴板数据的指针后,会解析数据以提取应用程序、主题和项名称。
客户端应用程序确定其与服务器应用程序之间是否已存在有关主题的对话。 如果对话确实存在,则客户端会检查数据项是否已存在链接。 如果存在此类链接,客户端将向用户显示消息框;否则,它会调用自己的 SendAdvise 函数,将 WM_DDE_ADVISE 消息发送到该项的服务器。
如果客户端和服务器之间尚不存在有关主题的对话,则客户端会先调用自己的 SendInitiate 函数,以广播 WM_DDE_INITIATE 消息来请求对话,然后调用自己的 FindServerGivenAppTopic 函数,以与代表服务器应用程序响应的窗口建立对话。 聊天开始后,客户端应用程序会调用 SendAdvise 来请求链接。
通知客户端数据已更改
当客户端使用 WM_DDE_ADVISE 消息建立链接,但未在 DDEDATA 结构中设置 fDeferUpd 成员(即等于零)时,则每次项值发生更改时,客户端都请求服务器发送数据项。 在这种情况下,服务器会以先前指定的格式呈现数据项的新值,并向客户端发送 WM_DDE_DATA 消息,如以下示例所示。
// Allocate the size of a DDE data header, plus data (a string),
// plus a <CR><LF><NULL>
size_t* pcch;
HRESULT hResult;
hResult = StringCchLength(szItemValue,STRSAFE_MAX_CCH, pcch);
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
if (!(hData = GlobalAlloc(GMEM_MOVEABLE,
sizeof(DDEDATA) + *pcch + 3)))
{
return;
}
if (!(lpData = (DDEDATA FAR*) GlobalLock(hData)))
{
GlobalFree(hData);
return;
}
lpData->fAckReq = bAckRequest; // as in original WM_DDE_ADVISE
lpData->cfFormat = CF_TEXT;
hResult = StringCchCopy(lpData->Value, *pcch +1, szItemValue); // copies value to be sent
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
// add CR/LF for CF_TEXT format
hResult = StringCchCat(lpData->Value, *pcch + 3, "\r\n");
if (FAILED(hResult))
{
// TODO: Write error handler.
return;
}
GlobalUnlock(hData);
if ((atomItem = GlobalAddAtom(szItemName)) != 0)
{
if (!PostMessage(hwndClientDDE,
WM_DDE_DATA,
(WPARAM) hwndServerDDE,
PackDDElParam(WM_DDE_DATA, (UINT) hData, atomItem)))
{
GlobalFree(hData);
GlobalDeleteAtom(atomItem);
FreeDDElParam(WM_DDE_DATA, lParam);
}
}
if (atomItem == 0)
{
// Handle errors.
}
在此示例中,客户端会相应地处理项值。 如果设置了项的 fAckReq 标志,客户端会向服务器发送正 WM_DDE_ACK 消息。
当客户端建立此链接,并设置了 fDeferUpd 成员(即等于 1)时,每次数据发生更改时,客户端都请求仅发送通知,而不发送数据本身。 在这种情况下,当项值发生更改时,服务器不会呈现该值,而只是向客户端发送含 null 数据句柄的 WM_DDE_DATA 消息,如以下示例所示。
if (bDeferUpd) // check whether flag was set in WM_DDE_ADVISE
{
if ((atomItem = GlobalAddAtom(szItemName)) != 0)
{
if (!PostMessage(hwndClientDDE,
WM_DDE_DATA,
(WPARAM) hwndServerDDE,
PackDDElParam(WM_DDE_DATA, 0,
atomItem))) // NULL data
{
GlobalDeleteAtom(atomItem);
FreeDDElParam(WM_DDE_DATA, lParam);
}
}
}
if (atomItem == 0)
{
// Handle errors.
}
如有必要,客户端可以发出正常的 WM_DDE_REQUEST 消息来请求数据项的最新值,也可以忽略服务器发来的指明数据已更改的通知。 在任一情况下,如果 fAckReq 等于 1,则客户端应向服务器发送正 WM_DDE_ACK 消息。
终止数据链接
如果客户端请求终止特定数据链接,客户端会向服务器发送 WM_DDE_UNADVISE 消息,如以下示例所示。
if ((atomItem = GlobalAddAtom(szItemName)) != 0)
{
if (!PostMessage(hwndServerDDE,
WM_DDE_UNADVISE,
(WPARAM) hwndClientDDE,
PackDDElParam(WM_DDE_UNADVISE, 0, atomItem)))
{
GlobalDeleteAtom(atomItem);
FreeDDElParam(WM_DDE_UNADVISE, lParam);
}
}
if (atomItem == 0)
{
// Handle errors.
}
服务器会检查客户端当前是否有指向此对话中特定项的链接。 如果存在链接,服务器会向客户端发送正 WM_DDE_ACK 消息;那么,服务器就不再需要发送有关该项的更新。 如果不存在链接,服务器会向客户端发送负 WM_DDE_ACK 消息。
WM_DDE_UNADVISE 消息指定数据格式。 零格式通知服务器停止指定项的所有链接,即使建立了多个热链接且每个链接都使用不同的格式也是如此。
若要终止对话的所有链接,客户端应用程序会向服务器发送一条含 null 项原子的 WM_DDE_UNADVISE 消息。 服务器确定对话当前是否至少建立了一个链接。 如果存在链接,服务器会向客户端发送正 WM_DDE_ACK 消息;那么,服务器就不再需要发送对话中的任何更新。 如果不存在链接,服务器会向客户端发送负 WM_DDE_ACK 消息。
在服务器应用程序中执行命令
应用程序可以使用 WM_DDE_EXECUTE 消息,促使在另一个应用程序中执行特定命令或一系列命令。 为此,客户端会向服务器发送一条包含命令字符串句柄的 WM_DDE_EXECUTE 消息,如以下示例所示。
HRESULT hResult;
if (!(hCommand = GlobalAlloc(GMEM_MOVEABLE,
sizeof(szCommandString) + 1)))
{
return;
}
if (!(lpCommand = GlobalLock(hCommand)))
{
GlobalFree(hCommand);
return;
}
hResult = StringCbCopy(lpCommand, sizeof(szCommandString), szCommandString);
if (hResult != S_OK)
{
// TODO: Write error handler.
return;
}
GlobalUnlock(hCommand);
if (!PostMessage(hwndServerDDE,
WM_DDE_EXECUTE,
(WPARAM) hwndClientDDE,
PackDDElParam(WM_DDE_EXECUTE, 0, (UINT) hCommand)))
{
GlobalFree(hCommand);
FreeDDElParam(WM_DDE_EXECUTE, lParam);
}
在此示例中,服务器会尝试执行指定的命令字符串。 如果成功,服务器会向客户端发送正 WM_DDE_ACK 消息;否则,会发送负 WM_DDE_ACK 消息。 此 WM_DDE_ACK 会重复使用在原始 WM_DDE_EXECUTE 消息中传递的 hCommand 句柄。
如果客户端的命令执行字符串请求服务器终止,则作为响应,服务器应发送正 WM_DDE_ACK 消息,然后在终止前发布 WM_DDE_TERMINATE 消息。 随 WM_DDE_EXECUTE 消息发送的所有其他命令都应同步执行;也就是说,仅在成功完成命令后,服务器才应发送 WM_DDE_ACK 消息。
终止对话
客户端或服务器都可以随时发出 WM_DDE_TERMINATE 消息来终止对话。 同样,客户端和服务器应用程序都应准备好随时接收此消息。 在关闭之前,应用程序必须终止其所有对话。
在以下示例中,终止对话的应用程序会发布 WM_DDE_TERMINATE 消息。
PostMessage(hwndServerDDE, WM_DDE_TERMINATE,
(WPARAM) hwndClientDDE, 0);
这会通知其他应用程序发送应用程序不会发送其他消息,并且接收方可以关闭其窗口。 在所有情况下,接收方都应发送 WM_DDE_TERMINATE 消息来及时做出响应。 接收方不得发送负、忙碌或正 WM_DDE_ACK 消息。
应用程序在 DDE 对话中将 WM_DDE_TERMINATE 消息发送给合作伙伴后,不得响应来自该合作伙伴的消息,因为合作伙伴可能已销毁将响应发送到的窗口。
如果应用程序在发布 WM_DDE_TERMINATE 后收到 WM_DDE_TERMINATE 以外的 DDE 消息,则应释放与收到的消息关联的所有对象,但未设置 fRelease 成员的 WM_DDE_DATA 或 WM_DDE_POKE 消息的数据句柄除外。
当应用程序即将终止时,应在完成 WM_DESTROY 消息处理之前结束所有活动的 DDE 对话。 但是,如果应用程序未结束其活动的 DDE 对话,则当窗口被销毁时,系统将终止与窗口关联的任何 DDE 对话。 以下示例演示服务器应用程序如何终止所有 DDE 对话。
void TerminateConversations(hwndServerDDE)
HWND hwndServerDDE;
{
HWND hwndClientDDE;
// Terminate each active conversation.
while (hwndClientDDE = GetNextLink(hwndClientDDE))
{
SendTerminate(hwndServerDDE, hwndClientDDE);
}
return;
}
BOOL AtLeastOneLinkActive(VOID)
{
return TRUE;
}
HWND GetNextLink(hwndDummy)
HWND hwndDummy;
{
return (HWND) 1;
}
VOID SendTerminate(HWND hwndServerDDE, HWND hwndClientDDE)
{
return;
}