对话管理
始终在客户端请求时建立客户端和服务器之间的对话。 建立对话后,每个合作伙伴都会收到标识对话的句柄。 合作伙伴在其他动态数据交换管理库 (DDEML) 函数中使用此句柄,以发送事务和管理对话。 客户端可以请求与单个服务器建立一个对话,也可以请求与一个或多个服务器建立多个对话。
以下主题介绍了应用程序如何建立新对话并获取有关现有对话的信息。
单个对话
通过调用 DdeConnect 函数,并指定字符串句柄来标识包含服务器应用程序的服务名称以及对话的主题名称的字符串,客户端应用程序可请求与一个服务器建立单个对话。 作为响应,DDEML 会将 XTYP_CONNECT 事务发送给每个如下服务器应用程序的动态数据交换 (DDE) 回调函数:已注册的服务名称与 DdeConnect 中指定的服务名称匹配,或已调用 DdeNameService 来关闭服务名称筛选。 服务器还可以在 DdeInitialize 函数中指定 CBF_FAIL_CONNECTIONS 筛选器标志,以筛选 XTYP_CONNECT 事务。 在 XTYP_CONNECT 事务期间,DDEML 会将服务名称和主题名称传递给服务器。 服务器必须检查名称,并在服务器支持服务名称和主题名称对时返回 TRUE,或者在不支持时返回 FALSE。
如果没有服务器对客户端的连接请求做出积极响应,则客户端会从 DdeConnect 收到 NULL,且不会建立任何对话。 如果服务器返回 TRUE,则已建立对话,并且客户端会收到一个对话句柄,即标识对话的 DWORD 值。 客户端在后续 DDEML 调用中使用句柄,以从服务器获取数据。 服务器收到 XTYP_CONNECT_CONFIRM 事务(除非服务器指定了 CBF_SKIP_CONNECT_CONFIRMS 筛选器标志)。 此事务会将对话句柄传递给服务器。
以下示例使用可识别服务名称 MyServer 的服务器请求有关“系统”主题的对话。 hszServName 和 hszSysTopic 参数是以前创建的字符串句柄。
HCONV hConv; // conversation handle
HWND hwndParent; // parent window handle
HSZ hszServName; // service name string handle
HSZ hszSysTopic; // System topic string handle
hConv = DdeConnect(
idInst, // instance identifier
hszServName, // service name string handle
hszSysTopic, // System topic string handle
(PCONVCONTEXT) NULL); // use default context
if (hConv == NULL)
{
MessageBox(hwndParent, "MyServer is unavailable.",
(LPSTR) NULL, MB_OK);
return FALSE;
}
在前面的示例中,DdeConnect 导致 MyServer 应用程序的 DDE 回调函数收到 XTYP_CONNECT 事务。
在下面的示例中,服务器将 DDEML 传递给服务器的主题名称字符串句柄与服务器支持的主题名称字符串句柄数组中的每个元素进行比较,以响应 XTYP_CONNECT 事务。 如果服务器找到匹配项,则会建立对话。
#define CTOPICS 5
HSZ hsz1; // string handle passed by DDEML
HSZ ahszTopics[CTOPICS]; // array of supported topics
int i; // loop counter
// Use a switch statement to examine transaction types.
// Here is the connect case.
case XTYP_CONNECT:
for (i = 0; i < CTOPICS; i++)
{
if (hsz1 == ahszTopics[i])
return TRUE; // establish a conversation
}
return FALSE; // Topic not supported; deny conversation.
// Process other transaction types.
如果服务器返回 TRUE 来响应 XTYP_CONNECT 事务,则 DDEML 会将 XTYP_CONNECT_CONFIRM 事务发送给服务器的 DDE 回调函数。 服务器可以通过处理此事务来获取对话的句柄。
客户端可以在调用 DdeConnect 时,为服务名称字符串句柄和/或主题名称字符串句柄指定 NULL,以建立通配符对话。 如果至少有一个字符串句柄为 NULL,则 DDEML 会将 XTYP_WILDCONNECT 事务发送给所有 DDE 应用程序的回调函数(筛选 XTYP_WILDCONNECT 事务的回调函数除外)。 作为响应,每个服务器应用程序都应返回标识 HSZPAIR 结构的以 null 结尾的数组的数据句柄。 如果服务器应用程序尚未调用 DdeNameService 来注册其服务名称,并且筛选处于打开状态,则服务器不会收到 XTYP_WILDCONNECT 事务。 有关数据句柄的详细信息,请参阅数据管理。
该数组必须包含与客户端指定的对匹配的每个服务名称和主题名称对的一个结构。 DDEML 选择其中一对来建立对话,并将标识对话的句柄返回给客户端。 DDEML 将 XTYP_CONNECT_CONFIRM 事务发送给服务器(除非服务器筛选此事务)。 以下示例演示对 XTYP_WILDCONNECT 事务的典型服务器响应。
#define CTOPICS 2
UINT uType;
HSZPAIR ahszp[(CTOPICS + 1)];
HSZ ahszTopicList[CTOPICS];
HSZ hszServ, hszTopic;
WORD i, j;
if (uType == XTYP_WILDCONNECT)
{
// Scan the topic list and create an array of HSZPAIR structures.
j = 0;
for (i = 0; i < CTOPICS; i++)
{
if (hszTopic == (HSZ) NULL ||
hszTopic == ahszTopicList[i])
{
ahszp[j].hszSvc = hszServ;
ahszp[j++].hszTopic = ahszTopicList[i];
}
}
// End the list with an HSZPAIR structure that contains NULL
// string handles as its members.
ahszp[j].hszSvc = NULL;
ahszp[j++].hszTopic = NULL;
// Return a handle to a global memory object containing the
// HSZPAIR structures.
return DdeCreateDataHandle(
idInst, // instance identifier
(LPBYTE) &ahszp, // pointer to HSZPAIR array
sizeof(HSZ) * j, // length of the array
0, // start at the beginning
(HSZ) NULL, // no item name string
0, // return the same format
0); // let the system own it
}
客户端或服务器都可以随时调用 DdeDisconnect 函数来终止对话。 此函数会导致对话中合作伙伴的回调函数收到 XTYP_DISCONNECT 事务(除非合作伙伴指定了 CBF_SKIP_DISCONNECTS 筛选器标志)。 通常,应用程序使用 DdeQueryConvInfo 函数来获取有关已终止对话的信息,以响应 XTYP_DISCONNECT 事务。 回调函数从处理 XTYP_DISCONNECT 事务返回后,对话句柄将不再有效。
若客户端应用程序在其 DDE 回调函数中收到 XTYP_DISCONNECT 事务,则可以调用 DdeReconnect 函数来尝试重新建立对话。 客户端必须从其 DDE 回调函数内调用 DdeReconnect。
多个对话
客户端应用程序可以使用 DdeConnectList 函数来确定系统中是否有任何相关服务器。 客户端在调用 DdeConnectList 时指定服务名称和主题名称,导致 DDEML 将 XTYP_WILDCONNECT 事务广播到与服务名称匹配的所有服务器的 DDE 回调函数(筛选事务的回调函数除外)。 服务器的回调函数应返回一个数据句柄,该句柄标识 HSZPAIR 结构的以 null 结尾的数组。 该数组应包含与客户端指定的对匹配的每个服务名称和主题名称对的一个结构。 DDEML 为服务器填充的每个 HSZPAIR 结构建立对话,并将对话列表句柄返回给客户端。 服务器通过 XTYP_CONNECT 事务接收对话句柄(除非服务器筛选此事务)。
在调用 DdeConnectList 时,客户端可以为服务名称和/或主题名称指定 NULL。 如果服务名称为 NULL,则支持指定主题名称的系统中的所有服务器都会响应。 与每个响应服务器建立对话,包括同一服务器的多个实例。 如果主题名称为 NULL,则会在与服务名称匹配的每个服务器识别的每个主题上建立对话。
客户端可以使用 DdeQueryNextServer 和 DdeQueryConvInfo 函数来标识响应 DdeConnectList 的服务器。 DdeQueryNextServer 会返回对话列表中的下一个对话句柄,而 DdeQueryConvInfo 使用有关对话的信息填充 CONVINFO 结构。 客户端可以保留需要的对话句柄,并放弃对话列表中的其余部分。
以下示例使用 DdeConnectList 与支持“系统”主题的所有服务器建立对话,然后使用 DdeQueryNextServer 和 DdeQueryConvInfo 函数获取服务器的服务名称字符串句柄,并将其存储在缓冲区中。
HCONVLIST hconvList; // conversation list
DWORD idInst; // instance identifier
HSZ hszSystem; // System topic
HCONV hconv = NULL; // conversation handle
CONVINFO ci; // holds conversation data
UINT cConv = 0; // count of conv. handles
HSZ *pHsz, *aHsz; // point to string handles
// Connect to all servers that support the System topic.
hconvList = DdeConnectList(idInst, NULL, hszSystem, NULL, NULL);
// Count the number of handles in the conversation list.
while ((hconv = DdeQueryNextServer(hconvList, hconv)) != NULL)
cConv++;
// Allocate a buffer for the string handles.
hconv = NULL;
aHsz = (HSZ *) LocalAlloc(LMEM_FIXED, cConv * sizeof(HSZ));
// Copy the string handles to the buffer.
pHsz = aHsz;
while ((hconv = DdeQueryNextServer(hconvList, hconv)) != NULL)
{
DdeQueryConvInfo(hconv, QID_SYNC, (PCONVINFO) &ci);
DdeKeepStringHandle(idInst, ci.hszSvcPartner);
*pHsz++ = ci.hszSvcPartner;
}
// Use the handles; converse with the servers.
// Free the memory and terminate the conversations.
LocalFree((HANDLE) aHsz);
DdeDisconnectList(hconvList);
应用程序可以调用 DdeDisconnect 函数来终止对话列表中的单个对话。 应用程序可以调用 DdeDisconnectList 函数来终止对话列表中的所有对话。 这两个函数都会导致 DDEML 将 XTYP_DISCONNECT 事务发送给每个伙伴的 DDE 回调函数。 DdeDisconnectList 会为列表中的每个对话句柄发送 XTYP_DISCONNECT 事务。
客户端可以将现有对话列表句柄传递给 DdeConnectList,以检索对话列表中的对话句柄列表。 枚举进程会从列表中删除已终止对话的句柄,并添加适合指定服务名称和主题名称的非重复对话。
如果 DdeConnectList 指定现有对话列表句柄,该函数将创建一个新的对话列表,其中包含任何新对话的句柄以及现有列表中的句柄。
如果存在重复对话,DdeConnectList 会尝试阻止对话列表中的重复对话句柄。 重复对话是指同一服务名称和主题名称上具有同一服务器的第二个对话。 两个这样的对话将有不同的句柄,但它们将标识相同的对话。