Quick and Easy DDE Server

Herman (Mr. DDE) Rodent
Microsoft Developer Network Technology Group

Created: November 30, 1992

Click to open or copy the files in the DDEServ sample application for this technical article.

Click to open or copy the files in the DDERecon sample application for this technical article.

Abstract

Adding dynamic data exchange (DDE) server support to an application has never been easier. This article describes a code module you can include in your own application that makes adding DDE server support trivial. The code module makes use of the dynamic data exchange management library (DDEML) to implement the DDE protocol and provides the following features:

This article requires considerable familiarity with DDE concepts—it's not a good place to start learning about DDE. Please refer to the Microsoft® Windows™ version 3.1 Software Development Kit (SDK) Programmer's Reference, Volume 1: Overview, Part 2: Extension Libraries for a general introduction to DDE and DDEML. The following articles on the Microsoft Developer Network CD (Technical Articles, Windows Articles, OLE and DDE Articles) provide more detail on various DDE topics:

Introduction

Creating a dynamic data exchange (DDE) server from scratch or adding DDE server support to an existing application can be a real nightmare. The DDE protocol is quite complex, and correctly implementing it is not a trivial matter. The dynamic data exchange management library (DDEML) has improved matters considerably. DDEML is a support library that implements the DDE protocol and provides an application programming interface (API) to the developer to simplify DDE implementation and make the resulting application conform more exactly to the DDE protocol.

DDEServ, the source code sample included with this article, goes further than the DDEML API set and provides a single code module that fully implements a DDE server. The code module exports a number of functions that will greatly simplify implementation of a DDE server.

This article describes the process of creating a DDE server from scratch using the sample code module and DDEML to provide most of the basic functionality. The server can support an arbitrary number of topics and topic/item pairs. The code maintains a set of simple linked lists and provides functions to add and remove topics and topic/item pairs at any time. The code tracks conversations so that they can be automatically terminated if their topic is removed while a conversation is still active.

The sample code module includes a parser for DDE execute commands that conforms to the syntax used by applications such as Microsoft® Excel and the Windows™ Program Manager. Functions are provided to allow commands to be attached to or removed from a topic at any time.

The functions provided by the sample code are listed below in the "Function Description" section. The structures used to construct the various lists are documented in the "Data Types and Structures" section.

Creating a DDE Server

This section gives a step-by-step description of adding DDE server support to an application. The sample code has #ifdef blocks, which follow the steps described here. You can test the steps in the sample code by changing the #define STEP_n statements in the beginning of the DDESERV.C file and then building the server. Here's what the code at the start of the DDESERV.C module looks like if you are preparing to build the sample for Step 1:

   #define STEP_1           // Initialization and termination
// #define STEP_2           // Topic/Item pairs
// #define STEP_3           // Additional System topic items
// #define STEP_4           // Execute commands

Getting Prepared

Before you start modifying your own code, I suggest you take a look at DDEServ, one of the two sample applications that accompanies this technical article, and follow the steps suggested here to familiarize yourself with what is going on at each step. Later, when you make these modifications to your own code, you will be familiar with the behavior you are expecting and have some practice at testing a server.

You should start by building the sample server application without any of the STEP_ statements defined. The application should start but have no DDE service registered. You can verify this by running DDERecon, the other sample application that accompanies this article, and seeing that DDEServ is not listed as a DDE service.

Step 1. Initialization and Termination

The first step in implementing DDE server support is to add a call to InitializeDDE. An ideal time to do this is right after the main window of the application has been created. The InitializeDDE function calls DDEML to register the application with the library and get the instance identifier from DDEML. The code then goes on to add a standard set of System topic items and finally to register the name of the service the server will provide.

Note   The sample code supports only one service per server.

The initialization call looks like this:

    InitializeDDE(hInstance,
                  SZ_SERVICENAME,
                  &dwDDEInst, 
                  NULL,
                  0);

When the application receives the WM_DESTROY message, it should call UninitializeDDE, which unregisters the service name, frees all the resources used, and releases DDEML.

The call looks like this:

    case WM_DESTROY:
        UninitializeDDE();
        PostQuitMessage(0);
        break;

Having added these two calls to initialize the server and close it down when the application exits, you can now compile the code and test it. You can test its DDE capabilities by running the DDERecon sample application. DDERecon should show the service name in the services list and should show that the minimum System topic support is working. Figure 1 shows what the DDERecon connect dialog box, Create DDE Link, should look like for a Step 1 build of the DDEServ sample application.

Figure 1. The DDERecon connect dialog box shows a Step 1 implementation of DDEServ.

Step 2. Topic/Item Pairs

The next step is to add support for each topic/item pair your server supports. This code is added to the existing initialization code. To add a topic/item pair, call the AddDDEItem function. It is not necessary to make a call to AddDDETopic first. If the topic does not exist, AddDDEItem will create it. The sample application supports a topic called Info and an item called Value. The Value item is a 16-bit signed integer quantity that can be interrogated and set from a DDE client application.

The initialization code now includes this call:

    AddDDEItem(SZ_INFO, 
               SZ_VALUE,
               MyFormats,
               ValueRequest,
               ValuePoke);

MyFormats is a null-terminated list of Clipboard formats supported by the server. The DDEServ sample application supports only the CF_TEXT format. ValueRequest and ValuePoke are functions that will be called when a client tries, respectively, to access and alter the item data. Here's the request handler for the Value item:

HDDEDATA ValueRequest(UINT wFmt, HSZ hszTopic, HSZ hszItem)
{
    char buf[40];

    wsprintf(buf, "%d", giValue);
    return MakeCFText(wFmt, buf, hszItem);
}

The value is converted to a string and then to a DDE data item. The MakeCFText function is the skeleton of what might be a more complete piece of code. The sample version will only convert to CF_TEXT format. A more complete server might support other formats.

HDDEDATA MakeCFText(UINT wFmt, LPSTR lpszStr, HSZ hszItem)
{
    if (wFmt != CF_TEXT) return NULL;

    return DdeCreateDataHandle(dwDDEInst,
                               lpszStr,
                               lstrlen(lpszStr)+1,
                               0,
                               hszItem,
                               CF_TEXT,
                               NULL);
}

Try building the sample server with the STEP_2 statement defined. Run DDERecon again and see that the server now has an Info topic, and the Info topic has a Value item. Note that it also has Formats and TopicItemList items. These were added by the STDDDE module when the Info topic was added. Figure 2 shows the DDERecon connect dialog box at this point.

Figure 2. The DDERecon connect dialog box after the Info topic has been added in Step 2.

Step 3. Additional System Topic Items

One of the optional System topic items in a DDE server is the Help item. You can add items to the System topic in the same way you can add items to any topic. The sample application adds a Help item to the System topic with this call:

    AddDDEItem(SZDDESYS_TOPIC, 
               SZDDESYS_ITEM_HELP,
               MyFormats,
               SysHelpRequest,
               NULL);

The SysHelpRequest function formats a simple string of text and returns it to the client. Try defining STEP_3 in the sample and building it. Use DDERecon to view the System topic items and connect to the Help item. Figure 3 shows the Help item in the System topics list of the DDERecon connect dialog box.

Figure 3. The DDERecon connect dialog box after the Help item is added to the System topic in Step 3.

Step 4. Execute Commands

DDE servers may optionally support a set of execute commands. The syntax of these commands and other details of their implementation is covered in the "DDE Execute Strings" technical article on the Microsoft Developer Network CD. The sample server, DDERecon, supports an Actions topic with a single Text command. A client can connect to the Actions topic and issue execute requests to it. The Text command follows Microsoft Excel syntax :

[Text(20,30,"Hi there Tiger!")]

The first argument is the x ordinate, the second is the y ordinate, and the third is the text itself. The text string is output to the client area of the server main window. The coordinates are in pixels.

The STDDDE.C module provides a command parser for execute function requests. It assumes that the syntax of these requests conforms to the Microsoft Excel standard. The code to add a command looks like this:

    AddDDEExecCmd(SZ_CMDTOPIC,  "Text",  TextFn,  3,  3);

Note that the topic is added automatically if it doesn't already exist. The fourth and fifth arguments describe the minimum and maximum number of arguments, respectively, that the Text command takes. The command processing functions receive their arguments in a list rather like the conventional C argc, argv method. Here's the handler for the Text command:

BOOL FAR TextFn(PDDETOPICINFO pTopic,
             LPSTR pszResult,
             UINT uiResultSize,
             UINT uiNargs,
             LPSTR FAR *ppArgs)
{
    HDC hDC;
    int x, y;
    char buf[32];

    _fstrncpy(buf, ppArgs[0], sizeof(buf)-1);
    x = atoi(buf);
    _fstrncpy(buf, ppArgs[1], sizeof(buf)-1);
    y = atoi(buf);

    hDC = GetDC(ghwndMain);
    TextOut(hDC, x, y, ppArgs[2], _fstrlen(ppArgs[2]));
    ReleaseDC(ghwndMain, hDC);

    return TRUE;
}

The coordinates are converted to integer values by copying their string representations to local memory and using a C run-time function to convert them. TextOut is then used to write the text to the device context (DC) of the application's main window.

This function never fails, but of course there are those that do! If the function detects an error, it can return an error information string by copying it to the buffer pointed to by pszResult. If the client application has requested that return information be saved by using the Result command to name an item in which to save the data, then your return string will be saved in that item for later retrieval by the client. See the "DDE Execute Strings" technical article for more details about how this protocol works. The sample code module supports a reduced version of this protocol.

Advanced Functionality

The preceding section covered the basics of adding DDE server support to an application. This section describes some additional possibilities when using the STDDDE.C sample code module.

Dynamically Altering the Topic and Item Lists

A server need not have a fixed set of topics or topic/item pairs. The sample code module (STDDDE.C) provides the RemoveDDEItem and RemoveDDETopic functions to remove an item from a topic or to remove a complete topic. Note that removing all the items from a topic doesn't delete the topic. The STDDDE.C module tracks conversations, so it can disconnect them if their topic is deleted while they are still active.

As an example of where this may be used, consider an application with a multiple document interface (MDI) such as Microsoft Excel, which can open multiple files. For each open file, the application can create a topic so that the file can be manipulated by other DDE requests. When a file is opened, its path is added as a topic, and when the file is closed, that topic is removed. If you have a copy of Microsoft Excel, try opening a number of spreadsheets and running the DDERecon application to view the Microsoft Excel topic list. Figure 4 shows an example of the topics Microsoft Excel creates.

Figure 4. Microsoft Excel creates topics for each file it has open.

Dynamically Adding and Removing Execute Commands

Commands, too, may be added to or removed from a topic at any time. They are added by calling AddDDEExecCmd and removed by calling RemoveDDEExecCmd. If you intend to have a dynamically varying command list for your server, you should consider how a client application is going to be able to see what the currently available command set is. There is no standard way of doing this, but you might consider adding another item such as CommandList to the topic that supports the commands. Another way to do this is to consider the set of commands as a protocol supported by the server, give this protocol a name, and include it in the System topic protocols item list. You can publish the command set for your protocol, and client applications can interrogate your server to see if it supports the named protocol.

Topic and Item Request Handlers

It is not necessary to have a different handler for every topic/item pair added with the AddDDEItem function. Many topic/item pairs can use the same handler. The handler is passed the HSZ of both the topic and the item, so it can use the FindTopicFromHsz and FindItemFromHsz functions to get pointers to topic or item information structures. These structures (DDETOPICINFO and DDEITEMINFO) can be expanded, according to your needs, to contain whatever information you require. Having received a pointer to the specific information, the handler can perform whatever generic processing is required.

It is possible to have a single handler for an entire topic. The AddDDETopic function has arguments to define generic topic Request and Poke handlers. If a topic/item pair has a generic topic handler and also a specific item handler, the item handler will be used. If neither a generic topic handler nor a specific item handler is supplied, the STDDDE.C module code fails that request.

Making Advise Notifications

If a data item changes at the server, it can use the PostDDEAdvise function to notify all clients of the change. PostDDEAdvise takes a pointer to a DDEITEMINFO structure as its argument. You can use the FindItemFromHsz and FindItemFromName functions to return a structure pointer to a specific item. The FindTopicFromHsz and FindTopicFromName functions can locate a pointer to a DDETOPICINFO structure.

Working with Multiple Data Formats

If your server supports many Clipboard data formats, the GetCFIdFromName and GetCFFromId functions can be used to translate a Clipboard format ID to and from the string representation of its name. The STDDDE.C module contains standard string names for the defined Windows Clipboard formats.

Function Description

This section describes the APIs that are global in the STDDDE.C module. The code also contains a number of internal functions that are documented in the code itself. See the "Data Types and Structures" section below for details about the data structures referred to here.

AddDDEExecCmd

Adds an execute command processor to a topic.

Syntax

PDDEEXECCMDFNINFO  AddDDEExecCmd(pszTopic, pszCmdName, pfnExecCmd,
    uiMinArgs, uiMaxArgs)

Parameters

LPSTR  pszTopic

Pointer to a string containing the name of the topic to add the command to.

LPSTR  pszCmdName

Pointer to a string containing the name of the command to add.

PDDEEXECCMDFN  pfnExecCmd

Pointer to a function to handle the command request.

UINT  uiMinArgs

Minimum number of arguments that are valid for this command.

UINT  uiMaxArgs

Maximum number of arguments that are valid for this command.

Return Value

The return value is a pointer to a DDEEXECCMDFNINFO structure if the command is added successfully; otherwise, it is NULL.

Comments

If the topic does not exist, it will be created. If the command already exists, the information will be updated.

AddDDEItem

Adds an item to a topic.

Syntax

PDDEITEMINFO  AddDDEItem(pszTopic, pszItem, pFormatList, pfnRequest, pfnPoke)

Parameters

LPSTR  pszTopic

Pointer to a string containing the name of the topic to add the item to.

LPSTR  pszItem

Pointer to a string containing the name of the item to add.

LPWORD  pFormatList

Pointer to a list of valid formats.

PDDEREQUESTFN  pfnRequest

Pointer to an optional function to handle request requests for this item. If this function is not provided, the default action is to call the topic generic-request-processor function if present.

PDDEPOKEFN  pfnPoke

Pointer to an optional function to handle poke requests for this item. If this function is not provided, the default action is to call the topic generic-poke-processor function if present. 

Return Value

The return value is a pointer to a DDEITEMINFO structure if the item is added or NULL if not.

AddDDETopic

Adds a new topic and default processing for its item list and formats.

Syntax

PDDETOPICINFO  AddDDETopic(pszTopic, pfnExec, pfnRequest, pfnPoke)

Parameters

LPSTR  pszTopic

Pointer to a string containing the name of the topic.

PDDEEXECFN  pfnExec

Pointer to an optional execute command processing function. This argument may be NULL, in which case the standard execute command parser will be used to process the request.

PDDEREQUESTFN  pfnRequest

Pointer to an optional function to handle request requests for items of this topic. If this function is provided, it will be called for any item not having its own request function processor.

PDDEPOKEFN  pfnPoke

Pointer to an optional function to handle poke requests for items of this topic. If this function is provided, it will be called for any item not having its own poke function processor.

Return Value

The return value is a pointer to a DDETOPICINFO structure if the topic is added or NULL if not.

Comments

If the topic already exists, its information will be updated by the call.

FindExecCmdFromName

Finds a DDE execute command from its string name.

Syntax

PDDEEXECCMDFNINFO  FindExecCmdFromName(pTopic, pszCmd)

Parameters

PDDETOPICINFO  pTopic

Pointer to the DDETOPICINFO structure for the topic being searched.

LPSTR  pszCmd

Pointer to the string containing the name of the command to search for.

Return Value

The return value is a pointer to a DDEEXECCMDFNINFO structure if the command is found; otherwise, it is NULL.

Comments

The search is not case sensitive.

FindItemFromHsz

Finds an item in the items list of a given topic by searching for its HSZ name.

Syntax

PDDEITEMINFO  FindItemFromHsz(pTopic, hszItem)

Parameters

PDDETOPICINFO  pTopic

Pointer to the DDETOPICINFO structure for the topic being searched.

LPSTR  hszItem

HSZ of the item to search for.

Return Value

The return value is a pointer to a DDEITEMINFO structure if the item is found; otherwise, it is NULL.

Comments

The search is not case sensitive.

FindItemFromName

Finds an item in the items list of a given topic by searching for its string name.

Syntax

PDDEITEMINFO  FindItemFromName(pTopic, pszItem)

Parameters

PDDETOPICINFO  pTopic

Pointer to the DDETOPICINFO structure for the topic being searched.

LPSTR  pszItem

Pointer to the string containing the name of the item to search for.

Return Value

The return value is a pointer to a DDEITEMINFO structure if the item is found; otherwise, it is NULL.

Comments

The search is not case sensitive.

FindTopicFromHsz

Finds a topic in the topics list by searching for its HSZ value.

Syntax

PDDETOPICINFO  FindTopicFromHsz(hszName)

Parameter

HSZ  hszName

HSZ value to search for.

Return Value

The return value is a pointer to a DDETOPICINFO structure if the topic is found; otherwise, it is NULL.

Comments

The search is not case sensitive.

FindTopicFromName

Finds a topic in the topic list by searching for its string name.

Syntax

PDDETOPICINFO  FindTopicFromName(pszName)

Parameter

LPSTR  pszName

Pointer to a string containing the name to search for.

Return Value

The return value is a pointer to a DDETOPICINFO structure if the topic is found; otherwise, it is NULL.

Comments

The search is not case sensitive.

GetCFIdFromName

Gets a Clipboard format ID from its name.

Syntax

WORD  GetCFIdFromName(pszName)

Parameter

LPSTR  pszName

Pointer to a string containing the name of the format.

Return Value

The return value is either a standard format ID or the result of registering the name. The name supplied is always registered if it is not one of the standard ones.

GetCFNameFromId

Gets the text name of a Clipboard format from its ID.

Syntax

LPSTR  GetCFNameFromId(wFmt, pBuf, iSize)

Parameters

WORD  wFmt

Format tag to return the name for.

LPSTR  pBuf

Pointer to a buffer to receive the name.

int  iSize

Size of the buffer.

Return Value

The return value is a pointer to the return string.

Comments

This command supports both standard and registered Clipboard formats.

InitializeDDE

Initializes all the DDE lists for this server and initializes the DDEML DLL.

Syntax

BOOL  InitializeDDE(hInstance, pszServiceName, pdwDDEInst, pfnCustomCallback)

Parameters

HANDLE  hInstance

Instance handle of the calling application.

LPSTR  pszServiceName

Pointer to a string containing the name of the service.

LPDWORD  pdwDDEInst

Pointer to a DWORD variable to receive the instance identifier returned by DDEML. This argument is optional. It may be set to NULL.

PFNCALLBACK  pfnCustomCallback

Pointer to an optional custom callback function. This parameter may be set to NULL, in which case all the handling will be done by STDDDE.

Return Value

The return value is TRUE if the initialization succeeds and FALSE if it fails.

Comments

This function adds to the System topic a number of items that are supported transparently to the application.

PostDDEAdvise

Posts a DDE advise notice to DDEML.

Syntax

void  PostDDEAdvise(pItemInfo)

Parameter

PDDEITEMINFO  pItemInfo

Pointer to a DDEITEMINFO structure describing the item.

Return Value

There is no return value.

RemoveDDEExecCmd

Removes a command from a topic.

Syntax

BOOL  RemoveDDEExecCmd(pszTopic, pszCmdName)

Parameters

LPSTR  pszTopic

Pointer to a string containing the name of the topic to remove the command from.

LPSTR  pszCmdName

Pointer to a string containing the name of the command to remove.

Return Value

The return value is TRUE if the command is removed, FALSE if not.

RemoveDDEItem

Removes an item from a topic.

Syntax

BOOL  RemoveDDEItem(pszTopic, pszItem)

Parameters

LPSTR  pszTopic

Pointer to a string containing the name of the topic to remove the item from.

LPSTR  pszItem

Pointer to a string containing the name of the item to remove.

Return Value

The return value is TRUE if the item is removed, FALSE if not.

Comments

Removing all the items from a topic does not remove the topic.

RemoveDDETopic

Removes a topic from the topics list.

Syntax

BOOL  RemoveDDETopic(pszTopic)

Parameter

LPSTR  pszTopic

Pointer to a string containing the name of the topic.

Return Value

The return value is TRUE if the topic is removed, FALSE if not.

Comments

If there is a conversation active on this topic, it will be disconnected.

UninitializeDDE

Terminates use of the DDEML DLL.

Syntax

void  UninitializeDDE()

Return Value

There is no return value.

Comments

Any resources used by earlier calls are freed.

Data Types and Structures

This section describes the structures used to implement the various linked lists in the STDDDE.C module.

CFTAGNAME

This structure is used to store information about a Clipboard ID and its text name.

typedef struct  {
    WORD wFmt;
    LPSTR pszName;
} CFTAGNAME;

Fields

wFmt

Format ID.

pszName

Pointer to the string name.

DDECONVINFO

This structure is used to store information about a Clipboard ID and its text name.

typedef struct  {
    struct _DDECONVINFO FAR * pNext;
    HCONV hConv;
    HSZ hszTopicName;
    PDDEITEMINFO pResultItem;
} DDECONVINFO;

Fields

pNext

Pointer to the next one in the conversation list.

hConv

Handle to the conversation.

hszTopicName

HSZ for the topic of the conversation.

pResultItem

Pointer to a temporary result item used to support the Result topic.

DDEEXECCMDFNINFO

This structure is used to store information about a DDE execute command processor function.

typedef struct  {
    struct _DDEEXECCMDFNINFO FAR * pNext;
    struct _DDETOPICINFO FAR * pTopic;
    LPSTR pszCmdName;
    PDDEEXECCMDFN pFn;
    UINT uiMinArgs;
    UINT uiMaxArgs;
} DDEEXECCMDFNINFO;

Fields

pNext

Pointer to the next item in the list.

pTopic

Pointer to the topic it belongs to.

pszCmdName

Name of the command.

pFn

Pointer to the function that will be called to process the command.

uiMinArgs

Minimum number of arguments accepted by this command.

uiMaxArgs

Maximum number of arguments accepted by this command.

DDEITEMINFO

This structure is used to store information about a DDE item.

typedef struct  {
    struct _DDEITEMINFO FAR * pNext;
    LPSTR pszItemName;
    HSZ hszItemName;
    struct _DDETOPICINFO FAR * pTopic;
    LPWORD pFormatList;
    PDDEREQUESTFN pfnRequest;
    PDDEPOKEFN pfnPoke;
    HDDEDATA hData;
} DDEITEMINFO;

Fields

pNext

Pointer to the next item in the list of items.

pszItemName

Pointer to its string name.

hszItemName

DDE string handle for the name.

pTopic

Pointer to the topic it belongs to.

pFormatList

Pointer to a null-terminated list of Clipboard format words.

pfnRequest

Pointer to the item-specific request processor.

pfnPoke

Pointer to the item-specific poke processor.

hData

Data handle for this item. This is only used by STDDDE.C when the item is a special Result item, and may be used as required for other items.

DDESERVERINFO

This structure is used to store information about a DDE server that has only one service.

typedef struct  {
    LPSTR lpszServiceName;
    HSZ hszServiceName;
    PDDETOPICINFO pTopicList;
    DWORD dwDDEInstance;
    PFNCALLBACK pfnStdCallback;
    PFNCALLBACK pfnCustomCallback;
    PDDECONVINFO pConvList;
} DDESERVERINFO;

Fields

lpszServiceName

Pointer to the service string name.

hszServiceName

DDE string handle for the name.

pTopicList

Pointer to the topic list.

dwDDEInstance

DDE instance value.

pfnStdCallback

Pointer to standard DDE callback function.

pfnCustomCallback

Pointer to a custom DDE callback function.

pConvList

Pointer to the active conversation list.

DDETOPICINFO

This structure is used to store information about a DDE topic.

typedef struct  {
    struct _DDETOPICINFO FAR * pNext;
    LPSTR pszTopicName;
    HSZ hszTopicName;
    PDDEITEMINFO pItemList;
    PDDEEXECFN pfnExec;
    PDDEREQUESTFN pfnRequest;
    PDDEPOKEFN pfnPoke;
    PDDEEXECCMDFNINFO pCmdList;
} DDETOPICINFO;

Fields

pNext

Pointer to the next topic in the topic list.

pszTopicName

Pointer to its string name.

hszTopicName

DDE string handle for the name.

pItemList

Pointer to the item list for this topic.

pfnExec

Pointer to the generic execute processor for this topic.

pfnRequest

Pointer to the generic request processor for this topic.

pfnPoke

Pointer to the generic poke processor for this topic.

pCmdList

Pointer to the execute command list for this topic.