OLE(四):动态数据交换使用手册
初始化一个会话
使用DDE协议进行交互的两个应用程序这种行为称作参与一个DDE会话。
为了初始化一个动态数据交换(DDE)会话,客户程序发送一个 WM_DDE_INITIATE 消息。通常,客户程序会调用SendMessage函数并以 -1 作为第一个参数来广播这个消息的。如果它知道服务器程序的窗口句柄的话,就可以使用那个窗口句柄来直接发送消息。客户程序还得调用GlobalAddAtom函数给应用程序名和主题名准备些原子,通过这些空(通配符)原子,就可以与任意的应用程序和主题进行会话.下面展示了客户程序如何初始化一个会话,并假定应用程序名和主题名都已经指定:
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);
需要注意的是:如果使用了空原子,就不需要调用GlobalAddAtom和GlobalDeleteAtom函数了.
在此例中,客户程序创建两个全局原子,它们分别包含了服务器程序名和主题名.并把它们作为WM_DDE_INITIATE 消息的lParam 参数发送给服务器程序.函数SendMessage指定的窗口句柄如果是-1,就意味着向所有正在运行的应用程序发送这个消息;接收此消息的应用程序会依次的返回到系统,直到这一过程完成,此函数才返回到客户程序.这样就可以保证在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(atomApplication);
}
GlobalDeleteAtom(atomTopic);
}
if ((atomApplication == 0) || (atomTopic == 0))
{
// Handle errors.
}
当服务器程序响应一个WM_DDE_ACK消息时,客户程序应当保存服务器程序的窗口句柄,而这个窗口句柄会作为WM_DDE_ACK消息的wParam 参数被客户程序接收到,然后客户程序用这个窗口句柄发送所有后续的DDE消息到服务器程序。
如果客户程序在应用程序名和主题名上使用了一个NULL的原子,那么客户程序就会收到一个以上的服务器程序的正式确认。但是,即使客户程序没有使用NULL的原子,那也是有可能会收到多个DDE服务器程序的正式确认。服务器程序应当为每次会话使用唯一一个窗口。客户程序的窗口过程可以使用服务器程序的窗口句柄(由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 的剪贴板格式作为要请求的数据项的格式。
而接收方(服务器程序)必须删除数据项原子,但是如果调用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 成员,之后服务器程序填充数据对象并解锁数据(GlobalUnlock)并创建一个包含数据项名的全局原子。最后服务器程序通过调用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 成员(在DDEDATA 结构里)是1,那么客户程序必须向服务器程序发送一个有效的WM_DDE_ACK 消息,如下所示:
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_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);
在这个例子中,服务器程序调用 GlobalGetAtomName 函数来获取客户程序发送过来的数据项名,然后检测数据项是否有效以及数据项的格式是否正确(这里就是CF_TEXT)。如果数据项无效或者格式不正确,再或者数据的内存不能锁定,那么服务器程序就发送一个无效的回应给客户程序。在这种情况要注意的是,此时发送一个无效的回应是正确的,因为WM_DDE_POKE 消息总是假定fAckReq 成员是置为的(就是1),而服务器程序应该忽略该成员。
如果服务器程序发送一个无效的回应来响应WM_DDE_POKE 消息,那么客户程序应该释放由此消息指定的内存(但不是lParam 参数)。
建立一个永久性的数据链路
客户程序通过投递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会话,这样就可以在服务器程序里复制数据项,然后粘贴到客户程序里去。
当用户从“编辑”菜单里选择“复制”命令时,服务器程序就提供一个剪贴板格式的链接,这个格式是一个字符串,包含应用程序名,主题名和项名。以下是标准格式:
应用程序名\0主题名\0项名\0\0
一个空字符分割这些名字,两个空字符表示整个字符串结束。
客户和服务器程序都需要注册剪贴板格式的链接。如下:
cfLink = RegisterClipboardFormat("Link");
客户程序通过“编辑”菜单上的粘贴链接命令来支持剪贴板格式链接。用户选择这个命令时,客户程序会从剪贴板链接格式的数据析取应用程序名,主题名,项名。客户程序使用这些名字为应用程序和主题初始化一个会话。如果这样的会话不存在,客户程序会发送 WM_DDE_ADVISE 消息给服务器程序,并指定包含有剪贴板链接格式的数据的项名。
下面的例子就展示了用户选择粘贴链接命令时客户程序时如何响应的:
void DoPasteLink(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 成员为0,这样来建立一个链接时,客户程序就已经请求服务器程序每当在项值改变时就发送数据项。在这样的情况下,服务器程序就会用事先指定的格式把新的数据项用 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 标志是1,就发送一个有效的WM_DDE_ACK 消息个服务器程序。
当客户程序在fDeferUpd是1的情况下建立链接时,就会请求仅有的一个通知,当然不是数据本身,而是每次数据改变之时就发送过来(通知)。在这种情况下,每当项值改变时,服务器程序并不是提供数据值,而是简单的使用一个空数据句柄来发送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 消息指定数据格式,0指示服务器程序停止所有指定项的链接,即使是若干的热链接并且使用了不同的格式。
为了终止所有会话的链接,客户程序发送一个带有空原子的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消息,那么它应该释放所有跟收到的消息相关的对象,除非是WM_DDE_DATA 或者WM_DDE_POKE 消息的fRelease 成员为0时的数据句柄。
当应用程序终止时,在完成处理WM_DESTROY消息之前应该终止所有活动的DDE会话。然而,如果应用程序不终止所有活动的DDE会话,那么系统在窗口销毁之前会终止所有跟窗口相关的DDE会话。下面展示了服务器程序终止所有的DDE会话:
void TerminateConversations(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;
}