使用动态数据交换

本部分包含以下任务的代码示例:

启动对话

若要启动动态数据交换(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 原子,则无需使用 GlobalAddAtomGlobalDeleteAtom 函数。 在此示例中,客户端应用程序分别创建两个全局原子,其中包含服务器的名称和主题的名称。

 

客户端应用程序在消息的 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 atom,则希望应用程序从多个服务器应用程序接收确认。 多个确认也可能来自 DDE 服务器的多个实例,即使客户端应用程序不使用 NULL 原子也是如此。 服务器应始终对每个会话使用唯一窗口。 客户端应用程序中的窗口过程可以使用服务器窗口的句柄(作为 WM_DDE_INITIATElParam 参数提供),以便跟踪多个对话。 这样,单个客户端窗口就可以处理多个对话,而无需终止并重新连接每个会话的新客户端窗口。

传输单项

建立 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_PTR) 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_PTR) &hData, 
    (PUINT_PTR) &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_PTR) 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_PTR) &hPokeData, 
    (PUINT_PTR) &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_PTR) 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 数据句柄,否则包含格式信息。 因此,服务器必须拒绝已链接的项的所有暖链接,并且必须拒绝具有暖链接的项的所有链接。 另一种解释可能是,当请求同一数据项的第二个链接时,服务器将更改链接的格式和热状态或暖状态。

通常,客户端应用程序不应一次尝试为数据项建立多个链接。

支持热数据链接或暖数据链接的应用程序通常支持名为 Link 的已注册剪贴板格式。 与应用程序的“复制和粘贴链接”命令关联时,此剪贴板格式使用户只需在服务器应用程序中复制数据项并将其粘贴到客户端应用程序中,即可在应用程序之间建立 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_PTR) 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_PTR) 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_DATAWM_DDE_POKE消息中未设置fRelease成员的数据句柄。

当应用程序即将终止时,它应在完成 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; 
}