22.4.3 Establishing a Permanent Data Link

A client application can use DDE to establish a link to an item in a server application. Once such a link is established, the server sends periodic updates of the linked item to the client (typically, whenever the value of the item changes). Thus, a permanent data stream is established between the two applications; this data stream remains in place until it is explicitly disconnected.

22.4.3.1 Initiating a Data Link

The client initiates a data link by sending a WM_DDE_ADVISE message, as follows:

if (!hOptions = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,
        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,
            hwndClientDDE,
            MAKELONG(hOptions, atomItem))) {
        GlobalDeleteAtom(atomItem);
        GlobalFree(hOptions);
    }
}

if (atomItem == 0) {
    .
    . /* error handling */
    .

}

In this example, the client application sets the fDeferUpd flag of the WM_DDE_ADVISE message to FALSE. This directs the server application to send the data to the client whenever the data changes.

If the server has access to the item and can render it in the requested format, the server notes the new link (recalling the flags specified in hOptions) and sends the client a positive WM_DDE_ACK message. From then on, until the client issues a matching WM_DDE_UNADVISE message, the server sends the new data to the client every time the value of the item changes in the server application.

If the server is unable to service the WM_DDE_ADVISE request, it sends the client a negative WM_DDE_ACK message.

22.4.3.2 Initiating a Data Link with the Paste Link Command

Applications that support hot or warm data links typically support a registered clipboard format named Link. When associated with the application's Copy and Paste Link commands, this clipboard format allows the user to establish DDE conversations between applications simply by copying a data item in the server application and pasting it into the client application.

A server application supports the Link clipboard format by placing in the clipboard a string containing the application, topic, and item names when the user chooses the Copy command from the Edit menu. Following is the standard Link format:

application\0topic\0item\0\0

A single null character separates the names, and two null characters terminate the entire string.

Both the client and server applications must register the Link clipboard format, as shown:

cfLink = RegisterClipboardFormat("Link");

A client application supports the Link clipboard format by means of a Paste Link command on its Edit menu. When the user chooses this command, the client application parses the application, topic, and item names from the Link-format clipboard data. Using these names, the client application initiates a conversation for the application and topic, if such a conversation does not already exist. The client application then sends a WM_DDE_ADVISE message to the server application, specifying the item name contained in the Link-format clipboard data.

Following is an example of a client application's response to the Paste Link command being chosen:

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];
    int    nBufLen;

    if (OpenClipboard(hwndClientDDE)) {
        if (!(hData = GetClipboardData(cfLink)) ||
                !(lpData = GlobalLock(hData))) {
            CloseClipboard();
            return;
        }

        /* Parse the clipboard data.*/

        if ((nBufLen = lstrlen(lpData)) >= APP_MAX_SIZE) {
            CloseClipboard();
            GlobalUnlock(hData);
            return;
        }
        lstrcpy(szApplication, lpData);
        lpData += (nBufLen + 1); /* skips over null */
        if ((nBufLen = lstrlen(lpData)) >= TOPIC_MAX_SIZE) {
            CloseClipboard();
            GlobalUnlock(hData);
            return;
        }
        lstrcpy(szTopic, lpData);
        lpData += (nBufLen + 1); /* skips over null */
        if ((nBufLen = lstrlen(lpData)) >= ITEM_MAX_SIZE) {
            CloseClipboard();
            GlobalUnlock(hData);
            return;
        }
        lstrcpy(szItem, lpData);
        GlobalUnlock(hData);
        CloseClipboard();

        if (hwndServerDDE =
                FindServerGivenAppTopic(sszApplication, zTopic)) {

            /* 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 {

            /* Must initiate a new conversation first. */

            SendInitiate(szApplication, szTopic);
            if (hwndServerDDE =
                    FindServerGivenAppTopic(szApplication, szTopic))
                SendAdvise(hwndServerDDE, szItem);
        }
    }
    return;
}

In this example, the client application opens the clipboard and determines whether the clipboard contains data in the Link format (cfLink) that it had previously registered. If not, or if it cannot lock the data in the clipboard, the client returns.

After the client application has retrieved a pointer to the clipboard data, it parses the data to extract the application, topic, and item names.

The client application determines whether a conversation on the topic already exists between it and the server application. If a conversation does exist, the client application checks whether a link already exists for the data item. If such a link exists, the client displays a message box to the user; otherwise, it calls its own SendAdvise function to send a WM_DDE_ADVISE message to the server for the item.

If a conversation on the topic does not exist already between the client and the server, the client first calls its own SendInitiate function to broadcast the WM_DDE_INITIATE message to request a conversation and, second, calls its own FindServerGivenAppTopic function to establish the conversation with the window that responds on behalf of the server application. Once the conversation has begun, the client application calls SendAdvise to request the link.

22.4.3.3 Notifying the Client That Data Has Changed

When the client establishes a link by using the WM_DDE_ADVISE message—with the fDeferUpd flag not set (that is, equal to zero), the client has requested the server to send the data item each time the item's value changes. In such cases, the server renders the new value of the data item in the previously specified format and sends the client a WM_DDE_DATA message, as follows:

/*
 * Allocate the size of a DDE data header, plus data (a string),
 * plus a <CR><LF><NULL>
 */

if (!(hData = GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE),
        sizeof(DDEDATA) + strlen(szItemValue) + 3)))
    return;
if (!(lpData = (DDEDATA FAR*) GlobalLock(hData))) {
    GlobalFree(hData);
    return;
}
lpData->fAckReq = bAckRequest;  /* as specified in original     */
                                /* WM_DDE_ADVISE message        */
lpData->cfFormat = CF_TEXT;
lstrcpy(lpData->Value, szItemValue); /* copies value to be sent */
lstrcat(lpData->Value, "\r\n"); /* CR/LF for CF_TEXT format     */
GlobalUnlock(hData);
if ((atomItem = GlobalAddAtom(szItemName)) != 0) {
    if (!PostMessage(hwndClientDDE,
            WM_DDE_DATA,
            hwndServerDDE,
            MAKELONG(hData, atomItem))) {
        GlobalFree(hData);
        GlobalDeleteAtom(atomItem);
    }
}

if (atomItem == 0) {
     .
     . /* error handling */
     .

}

The client processes the item value as appropriate. If the fAckReq bit for the item is set, the client sends the server a positive WM_DDE_ACK message.

When the client establishes the link with the fDeferUpd flag set (that is, equal to 1), the client has requested that only a notification, not the data itself, be sent each time the data changes. In such cases, when the item value changes, the server does not render the value but simply sends the client a WM_DDE_DATA message with a null data handle, as follows:

if (bDeferUpd) {        /* checking whether the flag was originally */
                        /* set in the WM_DDE_ADVISE message         */

    if ((atomItem = GlobalAddAtom(szItemName)) != 0) {
        if (!PostMessage(hwndClientDDE,
                WM_DDE_DATA,
                hwndServerDDE,
                MAKELONG(0, atomItem))) { /* NULL data              */
            GlobalDeleteAtom(atomItem);
        }
    }
}

if (atomItem == 0) {
     .
     . /* error handling */
     .

}

As necessary, the client can then request the latest value of the data item by issuing a normal WM_DDE_REQUEST message, or it can simply ignore the notice from the server that the data has changed. In either case, if fAckReq is equal to 1, the client is expected to send a positive WM_DDE_ACK message to the server.

22.4.3.4 Terminating a Data Link

If the client requests that a specific data link be terminated, the client sends the server a WM_DDE_UNADVISE message, as follows:

if ((atomItem = GlobalAddAtom(szItemName)) != 0) {
    if (!PostMessage(hwndServerDDE,
            WM_DDE_UNADVISE,
            hwndClientDDE,
            MAKELONG(0, atomItem))) {
        GlobalDeleteAtom(atomItem);
    }
}
if (atomItem == 0) {
     .
     . /* error handling */
     .

}

The server checks whether the client currently has a link to the specific item in this conversation. If it does, the server sends the client a positive WM_DDE_ACK message; the server is then no longer required to send updates about the item. If the server has no such link, it sends a negative WM_DDE_ACK message.

To terminate all links for a conversation, the client sends the server a WM_DDE_UNADVISE message with a null item atom. The server determines whether the conversation has at least one link currently established. If it does, the server sends the client a positive WM_DDE_ACK message; the server then no longer has to send any updates in the conversation. If the server has no links in the conversation, it sends the client a negative WM_DDE_ACK message.