Connection Transactions
After the server enables the conversations requested via 
XTYP_CONNECT or XTYP_WILDCONNECT and one or more 
DDE conversations have been established, a 
XTYP_CONNECT_CONFIRM transaction occurs for each 
established conversation. This gives the server the handle of that 
conversation. 
When one participant decides to end the conversation, it calls 
DdeDisconnect. This generates an XTYP_DISCONNECT 
transaction that is sent to the conversation partner's callback. 

XTYP_CONNECT
XTYP_CONNECT transactions require minimal processing. 
Since the server wouldn't have received this transaction unless the 
client explicitly requested the server's service name, the service 
name doesn't require checking unless your server supports more 
than one service. Even then, checking is required only when the 
server needs to perform service-specific initialization. Unless the 
server shares a DDEML application instance with client code, you 
won't need to check the same instance flag in the dwData2 
parameter. The server should have already checked the topic at the 
beginning of the callback, so all it has to do is check the values of a 
few members associated with the conversation pointed to by 
dwData1. 
First check for a NULL value for dwData1. This indicates that 
the client is a raw DDE program. To be safe, you should also check 
for a 0 security code and the standard codepage (CP_WINANSI) in 
a server conversation. If the server requires a nonzero security 
code and/or only uses an OEM character set codepage, raw DDE 
clients will not be able to communicate with the DDEML server, 
and the connection must be refused. If dwData1 is not NULL, you 
should make sure that the dwSecurity, iCodePage, and wFlags 
members of the client's CONVCONTEXT structure match those of 
the server. It is probably unnecessary to check the wCountryID 
and dwLangID fields unless you want a fully internationalized 
application. If all values match, the connection can be made; if not, 
the connection should be refused. The wFlags member of the 
CONVCONTEXT structure should also be checked, even though 
this field's values have not yet been defined by Microsoft to make 
your application compatible with future versions of the DDEML. 
Set the wFlags field to 0 for the time being. 
Truly secure DDEML conversations require more than a static, 
nonzero value in the dwSecurity field. This level of security is 
worse than no security at all, because it gives users the illusion of 
security, it means more coding for DDEML client developers, and is 
a joke for hackers to penetrate. 
A more sophisticated security mechanism might include a 
separate conversation to authenticate the client. This can be done 
by the server when the client generates an XTYP_REQUEST 
transaction. The authentication conversation could use a zero value 
for ccInfo.dwSecurity; subsequent conversations would use a 
ccInfo.dwSecurity value provided by the server.
If the server determines that the connection should be permitted, 
its callback function should return TRUE. If the connection cannot 
be permitted, the callback should return FALSE. If the callback 
function returns TRUE when a conversation is established, 
DDEML sends an XTYP_CONNECT_CONFIRM transaction to the 
server. 

XTYP_WILDCONNECT
The XTYP_WILDCONNECT transaction requires a bit more 
processing than XTYP_CONNECT. The item string handle should 
be checked for either a NULL value or the name of a service 
provided by this server. If the item string handle is a non-NULL 
value that doesn't match the supported service, the connection 
should be refused. If the connection is permitted, the server should 
create an array of HSZPAIR structures to hold the list of 
service/topic pairs of interest to the client. Each service/topic pair 
should be assigned to a single HSZPAIR structure. The 
DdeCreateDataHandle function should allocate the array in the 
data format requested by the client. DdeAddData can be used to 
expand this array as additional service/topic pairs are requested. 
Both members of the last HSZPAIR structure in the array should 
contain NULL handles.
Once you're finished appending HSZPAIR structures to the 
array, its data handle should be returned from the callback. If the 
callback function returns with a valid DDEML data handle and 
DDEML establishes one or more DDE conversations, a 
XTYP_CONNECT_CONFIRM transaction is sent to the server for 
each conversation established. Multiple conversations are 
established for a given XTYP_WILDCONNECT transaction if it 
was generated by a client call to DdeConnectList.

XTYP_CONNECT_CONFIRM
One XTYP_CONNECT_CONFIRM transaction is passed to a 
server's callback function for each DDE conversation established. 
This transaction is generated by DDEML after the server callback 
returns from processing the XTYP_CONNECT and 
XTYP_WILDCONNECT transactions. Because the 
XTYP_CONNECT_CONFIRM transaction parameters include the 
conversation's service name and topic string handles, as well as the 
new conversation handle, it is useful for the server to maintain a 
private conversation information table to hold the information 
received with the XTYP_CONNECT_CONFIRM transaction. 

XTYP_DISCONNECT
As mentioned earlier, the XTYP_DISCONNECT transaction is 
sent to an application when its conversation partner calls the 
DdeDisconnect function. The application receiving this transaction 
may require conversation information that can be retrieved by 
calling the DdeQueryConvInfo function. This application can 
attempt to reconnect the conversation using the DdeReconnect 
function. 
The conversation handle passed to the callback during 
XTYP_DISCONNECT processing becomes invalid when the 
callback returns; at that point, the conversation has been 
terminated. 

XTYP_REGISTER and XTYP_UNREGISTER
The XTYP_REGISTER and XTYP_UNREGISTER transactions, 
which are both generated by the DdeNameService function, pass 
two instance-specific string handles to the callback function of the 
client application. These string handles point to the base service 
name (hsz1 callback argument) and the instance-specific service 
name (hsz2 callback argument) of the server being registered or 
unregistered. The base service name string is the same string 
passed to DdeNameService. The instance-specific service name 
takes the following form,

base_service_name: (xxxx)

where "xxxx" is a four-digit hexadecimal number that uniquely 
defines an instance of a registered or unregistered DDEML server. 
When a client callback function receives an XTYP_REGISTER 
transaction, the base service name of the transaction should be 
compared to the instance-independent string that has the service 
name of the server with the desired DDE conversation. If the 
strings match, a conversation may be initiated via DdeConnect 
with the specified server. DDEML responds to this call by 
attempting to establish a conversation with the first application 
instance of the named server. As an alternative, the client may 
pass the instance-specific service name to DdeConnect, in which 
case DDEML attempts to create a conversation with that server's 
specified instance only. 

XTYP_REQUEST
Clients may use the DdeClientTransaction function to request 
server data. DdeClientTransaction is called with a transaction type 
of XTYP_REQUEST, the handle of the client conversation, the 
string handle of the desired item, the required data format, and the 
timeout value. All other arguments may be set to NULL, although 
you may want to supply a valid pointer for the transaction status 
flags argument. 
The data format, conversation handle, topic string handle, and 
item string handle are passed to the server's callback function. The 
server should respond by returning a data handle created with 
DdeCreateDataHandle if it supports the requested topic and item 
or NULL if it doesn't. If the call to DdeClientTransaction sets the 
last transaction status flags argument to a valid, non-NULL value, 
the variable pointed to by that argument will contain DDE_FACK 
if DdeClientTransaction returned a non-NULL data handle, or 
DDE_FNOTPROCESSED if DdeClientTransaction returned 
NULL. 

XTYP_POKE
DdeClientTransaction may also be used to send an unsolicited 
piece of data to a server using the XTYP_POKE transaction. The 
client must first fill a buffer with the data to be sent to the server. 
Then a pointer or handle to the data should be passed to 
DdeClientTransaction for transmission to the server. If a data 
handle is used, it must be created with DdeCreateDataHandle, and 
the DdeClientTransaction argument that specifies the length of the 
data being passed should be set to 1 to indicate that it's a data 
handle not a pointer. This data length argument should be set to 
the length of the data if a pointer is used. The call to 
DdeClientTransaction should also specify the conversation handle, 
item string handle, data format, a transaction type of 
XTYP_POKE, and a time-out value in the parameter list. The 
transaction status flags argument (as always) is optional. 
If the server chooses to accept the XTYP_POKE transaction's 
data, the callback should return DDE_FACK. If the server does not 
accept the data because it's busy, the server callback should return 
DDE_FBUSY. If the server cannot accept the data for another 
reason, it should return DDE_FNOTPROCESSED. The server 
callback's return value is placed in the variable pointed to by the 
transaction status flags argument of DdeClientTransaction. 

XTYP_ADVSTART
A DDEML client may wish to receive continuous updates on the 
value of a data item supplied by a server. It does so by establishing 
a link with the desired server. 
DDEML clients start links by issuing an XTYP_ADVSTART 
transaction with the DdeClientTransaction function. The client 
passes the data format, conversation handle, item string handle, 
timeout value, a transaction type of XTYP_ADVSTART, and one or 
both of the XTYPF_NODATA and XTYPF_ACKREQ bitflags to 
DdeClientTransaction. XTYPF_NODATA specifies a "warm link," 
which causes XTYP_ADVDATA transactions to be sent to the 
client without actual data; the client may then request the actual 
data via an XTYP_REQUEST. By default, XTYP_ADVSTART 
causes the actual data to be sent with the XTYP_ADVDATA 
transaction. The XTYPF_ACKREQ flag causes the server to wait 
for the client to finish processing a given XTYP_ADVDATA 
transaction before transmitting another XTYP_ADVDATA 
transaction. This is useful for clients that don't want to miss 
updates when they perform time-consuming processing on advise 
data. This happens when a server transmits advise data faster 
than the client can process that data. 
After the client passes these parameters to 
DdeClientTransaction, the parameters are passed to the server 
callback along with the topic string handle. The server must 
determine whether to permit or deny the link request. The server 
returns TRUE to allow the link and FALSE to deny it. 

XTYP_ADVREQ
If the server finds that the value of a data item of interest within 
an advise loop has changed after a link has been started, it should 
call the DdePostAdvise function with the string handles of the topic 
and item. DdePostAdvise generates an XTYP_ADVREQ 
transaction that's passed to the server's callback function. This 
transaction passes the topic string handle, item string handle, and 
data format to the server callback function along with a count of 
remaining XTYP_ADVREQ transactions for the same topic, item 
and format. When the server callback receives the XTYP_ADVREQ 
transaction, it must retrieve the updated value and send it to the 
client in the requested format. It does this by creating a data 
handle for the requested data using DdeCreateDataHandle, and 
returning that handle from the server callback.
Because DDEML separates the discovery of an updated value 
from the transmission of that value, you can structure your 
DDEML programs in a modular fashion. Items of interest to advise 
loops may be maintained in separate modules, using different 
methods for detecting change in the item's value. They can take 
advantage of the same mechanism to communicate changes in an 
item's value back to the interested client. 

XTYP_ADVDATA
As mentioned earlier, advise data is passed to a client's callback 
function via an XTYP_ADVDATA transaction. The data handle 
passed to the callback function becomes invalid (that is, it's freed) 
after the client callback function returns. The client callback 
function should not free the data handle itself. 
If the client chooses to accept the data the callback should return 
DDE_FACK. If the client refused the data, the client callback 
should return DDE_FBUSY indicating that it is too busy to accept 
the data or DDE_FNOTPROCESSED if it cannot accept the data 
for another reason. 

XTYP_ADVSTOP
A client should end an advise loop by invoking 
DdeClientTransaction. The call to DdeClientTransaction should 
specify the conversation handle, item string handle, data format, a 
transaction type of XTYP_ADVSTOP, a time-out value, and a 
pointer to the transaction status flags variable in the parameter 
list. 
When the server receives this transaction, it stops the advise 
loop. The server callback function does not return a specific value 
from the XTYP_ADVSTOP transaction. (In this case, it returns 0.) 

XTYP_EXECUTE
DDEML clients may send command strings to DDEML servers 
for remote execution using the XTYP_EXECUTE transaction. The 
client calls DdeClientTransaction with either a pointer or handle to 
the command string, the length of the command string data 
argument, the conversation handle, data format, a transaction type 
of XTYP_EXECUTE, a time-out value, and a pointer to the 
transaction status flags variable in the parameter list. The item 
string handle argument to DdeClientTransaction should be NULL. 
If the command string is passed to DdeCreateTransaction as a data 
handle, DdeCreateDataHandle will have to be used to create the 
data handle. In this case, the item string handle argument of 
DdeCreateDataHandle should also be NULL. 
When the server's callback function receives the 
XTYP_EXECUTE transaction, the server should access the 
command string (which always arrives in the callback function as a 
data handle) using either DdeAccessData or DdeGetData. The 
server may then execute the command string, call 
DdeUnaccessData if necessary, and return. Return values are  
DDE_FACK if the command was successfully executed, 
DDE_FBUSY if the server was too busy to execute the command, 
or DDE_FNOTPROCESSED if the server could not execute the 
command for another reason. The return value of the server 
callback is placed in the variable pointed to by the transaction 
status flags argument of DdeClientTransaction. 

XTYP_ERROR
The XTYP_ERROR transaction indicates some sort of serious 
DDEML error. An error value is passed to the callback in the low-
order word of the dwData1 callback parameter. The only error 
value currently defined is DMLERR_LOW_MEMORY, which 
indicates a possible loss of transaction data or general system 
failure. A DDEML application should respond to by freeing any 
unused string or data handles and unblocking any currently 
blocked conversations, so DDEML can clear its transaction queue. 
The application can also elect to clean up and terminate after 
receiving this transaction. 

XTYP_XACT_COMPLETE
Completed client-initiated asynchronous transactions generate 
an XTYP_XACT_COMPLETE transaction. If the transaction was 
successful and the completed transaction generates data, the 
hData callback argument is a valid DDEML data handle. If the 
transaction was successful and generates no data, hData equals 
TRUE. If the transaction failed, hData is set to FALSE. 
Because DDEML automatically frees data handles sent via an 
XTYP_XACT_COMPLETE, the hData callback argument should 
not be freed. If the client wants to save the data pointed to by 
hData, it should use DdeAccessData or DdeGetData to read the 
data into a local buffer. If DdeAccessData is called, 
DdeUnaccessData must also be called once the client callback is 
done copying or processing the data.
DDE Monitoring under DDEML
We might find it useful to monitor DDE and DDEML 
conversations occurring within a given Windows system. 
Monitoring these conversations and tracking their use of system 
resources might be useful in debugging DDEML applications as 
well as implementing system auditing and security facilities. The 
DDEML provides the ability to track DDE(ML) objects that are 
created, used, and freed by other running DDE(ML) applications. 
DDEML applications that have this ability are referred to as 
monitor applications. The DDESpy utility (DDESPY.EXE) 
provided with the SDK is an example of a DDEML monitor 
application. 
Applications express their intent to become monitor applications 
by passing the APPCLASS_MONITOR flag to the DdeInitialize 
function along with other flags that indicate the events upon which 
the monitor application would like to eavesdrop. 
DDEML monitor applications receive notification of DDE and 
DDEML events through the XTYP_MONITOR transaction, which 
is passed to a callback function similar to those within DDEML 
clients and servers. The procedure-instance address of the 
monitor's callback is passed to DdeInitialize along with 
APPCLASS_MONITOR and the above mentioned event flags. 
A DDEML monitor application-instance cannot engage in DDE 
conversations. This restriction leads to the corollary that no 
DDEML APIs may be called to start or end conversations, or 
modify existing string and data handles; however, string and data 
handles passed to the monitor callback within the 
XTYP_MONITOR transaction may be inspected at will using 
DDEML functions such as DdeQueryString, DdeGetData, and 
DdeAccessData. 

DDEML Monitor Application Lifecycle
A DDEML monitor application first sets up a DDEML-
compatible environment by taking the steps discussed in the 
Preparing for DDEML section of this article. DdeInitialize should 
then be called with the APPCLASS_MONITOR flag combined with 
the desired monitor event flags, and the procedure-instance 
address of the monitor's callback function. 
Although one can say that Windows-based applications generally 
spend their lifetime processing user input and external events, 
DDEML monitor applications emphasize the latter responsibility. 
The monitor application receives notification of all DDE(ML) 
events within the system in which the monitor application 
expressed interest via DdeInitialize. This notification arrives in the 
form of the XTYP_MONITOR transaction, which incorporates 
event-specific data structures. The monitor application typically 
processes this transaction by displaying the data passed to the 
callback function within a window, or writing such data to an 
output device (disk, comm port, debug terminal, and so on). 
When the monitor application's message loop terminates, it 
should call DdeUninitialize to free the DDEML resources 
associated with the application-instance previously created by 
DdeInitialize. 
The lifecycle of a DDEML monitor application may be 
represented by the following outline. 

For each application-instance: 
    DdeInitialize( )
                  For each external DDE(ML) event:
                           Process parameters of XTYP_MONITOR transaction
    DdeUninitialize( )

Callback Function, XTYP_MONITOR Transaction, and Data Structures
Monitor callback functions have the same function prototype as 
DDEML client and server callback functions. Monitor callback 
functions are relatively simple, due to the fact that monitor 
applications only receive the XTYP_MONITOR transaction; 
nevertheless, all monitor callback functions should check incoming 
transactions for a transaction type of XTYP_MONITOR. 
Once the XTYP_MONITOR transaction type has been identified, 
the monitor callback may access the data referred to by the hData 
callback parameter using DdeAccessData (DdeGetData may be 
used instead of DdeAccessData, but the latter is slightly more 
convenient because it returns a pointer to the data along with the 
data's length in a single function call, unlike DdeGetData). hData 
refers to an event-specific data structure that communicates the 
details of a new DDE(ML) event to the monitor callback function. 
The type of the data structure varies according to the nature of the 
event, and is indicated by the value of the dwData2 callback 
parameter. A monitor event may represent any one of the event 
types specified within the most recent call to DdeInitialize.
So long as the monitor treats the event data as read-only, the 
monitor may process the event data as it sees fit. After transaction 
processing is complete, if DdeAccessData was called to access the 
transaction data, DdeUnaccessData must be called before the 
monitor callback function returns. 
The general form of the code within a DDEML monitor callback 
function may be represented by the following outline. 

switch( wType )
{
case XTYP_MONITOR:
               /* Access the event-specific data. */
               lpData = DdeAccessData( hData , &dwDataLength );
               /* Process all DDE(ML) events. */
               switch( dwData2 )
               {
               case MF_CALLBACKS:
                       Perform transaction event processing here.  
                       break;
               case MF_CONV:
                       Perform conversation event processing here.  
                       break;
               case MF_ERRORS:
                       Perform error event processing here.  
                       break;
               case MF_HSZ_INFO:
                       Perform string handle event processing here.  
                       break;
               case MF_LINKS:
                       Perform advise loop event processing here.  
                       break;
               case MF_SENDMSGS:
                       Perform sent message event processing here.  
                       break;
               case MF_POSTMSGS:
                       Perform posted message event processing here.  
                       break;
}
DdeUnaccessData( hData );
break;
default:
               Perform error processing here - we should never
               receive a non-XTYP_MONITOR transaction!
               break;
}
return (HDDEDATA)0;

DDEML Issues and Tips
Certain comments within ddeml.h pertaining to the CONVINFO 
structure are confusing. Four fields within the CONVINFO 
structure contain DDEML state information defined by 
corresponding sets of defined constants. The comments affixed to 
the structure definition accurately define the relationship between 
these fields and the appropriate groups of constants; the comments 
nearest two groups of constant definitions refer to non-existent 
fields within the CONVINFO structure. The comments referring to 
the non-existent fields should be ignored. 
For the sake of completeness, the comment above the DDE_ 
constants is also incorrect. The wStatus field uses the ST_ 
constants, not the DDE_ constants. 

Conversation Handle Assumptions
Don't assume that valid client and server conversation handles 
(hConv) are always nonzero. This assumption will bite you when 
you attempt to connect to a server because the hConv passed to the 
server callback is zero during XTYP_CONNECT transactions. The 
hConv passed to the server callback during the subsequent 
XTYP_CONNECT_CONFIRM and all following transactions 
should be nonzero. All hConv values passed to clients should be 
nonzero. 

Starting Transactions From Within DDEML Callbacks
When processing a transaction within a DDEML callback, it 
might be desirable to start another transaction. Transactions 
started from within DDEML callbacks should be asynchronous, 
because synchronous transactions, as the name states, will block 
until the transaction is complete. The client may lose transactions 
if the server completing the (original) asynchronous transaction 
sends data faster than the client can process that data. If the 
embedded transaction is synchronous and does not complete before 
several DDE messages accumulate in the client's message queue, 
updates could be lost when the message queue fills up. In general, 
the principles used within the development of an interrupt handler 
may be profitably applied to the development of DDEML 
callbacksspecifically, store the incoming data someplace quickly 
and, if the data needs further processing that cannot be completed 
quickly, batch up the data and process it later. This strategy will 
allow a DDEML application to avoid missed updates, and in this 
case, leads us to the conclusion that embedded transactions should 
be asynchronous since asynchronous transactions can be started 
quickly and left to complete on their own. 

Item Handle Problems
We might find that a client times out on a synchronous 
transaction after the server appears to have done its job. Judicious 
use of DDESpy for simultaneous monitoring of transactions and 
messages often hints at the answer to this problem. Although 
DDESpy shows you that the data your transaction created appears 
as you expect, the cfFormat field of the DDEDATA structure within 
the WM_DDE_DATA message might contain a bizarre value, and 
the data that follows will not resemble the data your transaction 
created. This is a symptom of an incorrect (that is, NULL) value for 
the hszItem parameter in the call to DdeCreateDataHandle used to 
create the data returned from the server callback. Similar 
problems occur when sending an XTYP_POKE transaction to the 
server without using a correct DdeCreateDataHandle hszItem 
parameter. Creating a string handle for the item using 
DdeCreateStringHandle, then using that handle within calls to 
DdeCreateDataHandle will solve these problems. 

String and Data Handle Cleanup
Make certain that you clean up after yourself when you're 
finished using the DDEML. The main areas you should pay 
attention to are pending asynchronous transactions that seem to 
have gone into Never-Never Land, conversations that have outlived 
their usefulness, and string handles you may have forgotten to 
clean up. Don't be tempted to be lazy and not clean up transactions 
and conversations until the last minute. We might stand to free a 
sizeable amount of server-allocated memory through timely 
management of transactions and conversations. This is especially 
important when the server is a piece of code that we didn't write, 
since our only method of optimizing server resource usage will be 
to write an effecient client. 
Don't assume that the DDEML takes care of string handle 
cleanup for you. DDEML doesn't clean up string handles, although 
it will clean up data handles that have been created within a 
server and sent to a client as a part of an asynchronous 
transaction. Don't confuse the treatment of data handles and string 
handles. In particular, any service, topic, and item string handles 
you may have created must be explicitly freed using 
DdeFreeStringHandle. 
While we're on the topic of data handles, DDEML doesn't clean 
up data handles sent from server to client as a part of a 
synchronous transaction, and for good reasonDDEML has no 
way of knowing when you're finished with the data. DDEML can 
clean up asynchronous transaction data because DDEML assumes 
that the client processed or stored the data of interest when the 
client received the data within its callback function. This 
assumption allows DDEML to free data sent to a callback when 
that callback returns. Since synchronous data is returned directly 
by DdeClientTransaction and not sent to the client callback, the 
client may keep the data as long as it likes. The recommended way 
to approach this is to process or store the data upon receipt and 
then call DdeFreeDataHandle immediately to free DDEML 
resources. 
Now that we've discussed much of the theory and 
implementation of DDEML in small pieces, these parts should be 
assembled into a whole through the examination of a DDEML 
client and server that I've prepared specifically for this article. The 
demonstration client and server together comprise a text search 
utility with some interesting features that are made possible by the 
DDEML. 
Therefore, the major purpose of the programs accompanying this 
article is to illustrate the use of the DDEML within a small, 
useable, and perhaps useful tool. The achievement of this goal 
sometimes requires the omission of functionality that would be 
desirable within a productionworthy utility, especially when the 
implementation of that functionality has nothing to do with the 
DDEML. 

Client and Server Features
The client module provides an interface through which the user 
may enter one or more strings of text, which are then sent to the 
server so that the server may search one or more files for the 
specified text. 
Before a user may perform a text search with this utility, the 
user must specify configuration information by invoking the 
"Configure Search" dialog box, which is invoked by the "Search 
Configure..." menu item. After entering the desired information 
into the configuration information dialog, the user may execute a 
text search by invoking the "Search Find..." modeless dialog box. 
This dialog is one of the common dialogs provided by the 
COMMDLG DLL within Windows 3.1. The text entered into this 
dialog is forwarded to the server, and the server executes the 
actual text search. 
As the text search is executed by the server, the full pathname of 
each file containing the specified text is sent back to the client. The 
client displays the pathnames of the found files within its client 
area, and the user may select one of these files for display within 
the DDEML client. The user browses the found file's contents by 
either double-clicking on the desired pathname, or by selecting the 
desired pathname using the keyboard and then selecting the 
"Browse..." menu bar item. A modal dialog box will appear to 
display the contents of any selected ASCII file. The client does not 
perform any file format-specific processing when loading the file for 
display. The use of the modeless dialog for the entry of the search 
text allows additional search text to be entered and submitted to 
the server as the user views the contents of a found file. 
The user of the client may erase the list of found filenames by 
selecting the "Search Clear Window" menu item. The "DDE" menu 
bar item implements an interface to the System topic so that the 
user may see the type of information returned by the System 
topic's items. 

Transaction Model
One of the shortcomings of DDE not previously discussed is that 
information flow within DDE is generally unidirectional. DDE has 
no protocol for the transmission of data to the server that is in 
some way associated with a reply from that server. Because 
DDEML transactions are layered on top of DDE message pairs (for 
example, WM_DDE_REQUEST and its WM_DDE_ACK) and do 
not abstract transactions to a higher level, DDEML also suffers 
from this shortcoming. 
The demonstration application requires the client to send one 
string to the server at a time, and that the server reply with one or 
more pathnames of those files that contain the specified string 
data. Because we have a one-request-to-many-replies relationship 
and DDEML provides no native transaction support for this model, 
the search mechanism is implemented using a combination of 
advise loops and poke transactions. The client and server first start 
an advise loop to allow the server to send found file pathnames 
(replies) to the client. Once the server is prepared to return 
pathnames, the client pokes search strings into the server. The 
server then sends found file pathnames to the client within 
XTYP_ADVDATA transactions. The conversation and its advise 
loop are started when the first search string is entered by the user. 
The advise loop and conversation both persist until the client 
application closes, unless the user ends a search using the "Stop 
Find" menu item. In this event, the advise loop is stopped and 
restarted to stop the transmission of found filenames from server 
to client. 

Installation of Programs Without Source
The compiled demo programs DEMOCLI.EXE and 
DEMOSER.EXE and the file FILE.ICO should be copied into a 
directory on the user's fixed disk. 

Source Code
The files used to build the demonstration client and server may 
be divided into three categories: client-specific files, server-specific 
files and common files. Four directories are used in the build 
process: a "relative root" directory (for example, "DMLDEMO") and 
the COMMON, CLIENT, and SERVER directories, all three of 
which are subdirectories to the relative root directory. 
COMMON.LIB must be made from the files in the COMMON 
directory before the client and server programs can be made. 
Our discussion will concentrate on those portions of the code that 
call or are otherwise directly related to the DDEML. The 
remainder of the client and server code is composed of standard 
routines (hmmm, is that redundant???) that might be found in any 
application, such as dialog procedures and string processing 
functions. This latter category of code will not be discussed. 

A Tour Through the Demo Client
DEMOCLI.C contains the client's WinMain function. The only 
thing that appears to be out of the ordinary is the message loop. 
Those who peek ahead will see that the InitInstance function sets 
up a procedure-instance address so that MsgHookProc may be 
called by Windows, and that the MsgHookProc procedure-instance 
address is being called within the message loop. Since 
MsgHookProc's contents are almost identical to those of a standard 
message loop, there's little reason to replicate the MsgHookProc 
code within the message loop. The message loop instead calls 
MsgHookProc with a parameter list that makes it behave like the 
guts of a message loop. 
InitApp contains the check for the correct processor mode that 
DDEML requires if it might be used on a Windows 3.0 system. 
InitInstance contains the call to SetMessageQueue necessary to 
extend the client's message queue. After the queue has been 
successfully enlarged, DdeInitialize is called with the procedure-
instance address of the client callback function and the 
APPCMD_CLIENTONLY bitflag, thus telling the DDEML that 
this application wishes to be a DDEML client. 
After receiving an application-instance ID from DdeInitialize, we 
create any desired string handles by calling InitTopicAndItems, 
which in turn calls DdeCreateStringHandle. A message filter hook 
is then set to allow message processing during the modal loop that 
occurs during DDEML synchronous transactions. We then register 
the message by which the modeless "Find" dialog box 
communicates with the client, create our top-level window, and do 
a few other housekeeping chores before returning to WinMain and 
entering the message loop. After exiting from the message loop, 
WinMain calls a function called Cleanup. Cleanup is the inverse of 
InitInstance; it unhooks the message hook and calls 
DdeUninitialize. 
The client's window process function, WndProc, processes the 
WM_CREATE and WM_CLOSE messages, along with two 
application-specific messages (other messages are processed as 
well). WndProc's WM_CREATE processing creates a list box that 
fills the client area of the application's top-level window. This list 
box is filled with the pathnames of files found by the server 
application. 
The MM_FILE_FOUND message is received whenever a 
pathname is sent from server to client. The lParam accompanying 
this message points to the pathname of the found file. The 
MM_FILE_FOUND message is processed by adding the pathname 
to the list box displayed within the application's client area. 
The index of the message sent by the modeless Find dialog box is 
contained within wFindMessage. This message is sent when the 
user enters a search string and presses a button within the Find 
dialog. If the user presses the "Find Next" button, a search is 
initiated by calling HandleFindMsg (found within FIND.C), which 
in turn calls BeginDDESearchFind (within DDEHL.C) to start an 
advise loop for found filenames. It then pokes the search string into 
the server. 
WM_CLOSE causes all outstanding asynchronous transactions 
to be abandoned before terminating the search advise loop. 
Although the client application does not invoke asynchronous 
transactions, the call to DdeAbandonTransaction is included to 
demonstrate proper cleanup procedures when async transactions 
are used. In this case, the call to DdeAbandonTransaction does 
nothing. After DdeAbandonTransaction returns, if a nonzero 
conversation handle is returned by GetSearchConvHandle, the 
ongoing advise loop and conversation are ended through a call to 
EndDDESearchFind. 

CONFIG.C
The "Configure Search" dialog code (ConfigureDlgProc) is 
contained within CONFIG.C. ConfigureDlgProc invokes the 
"configure conversation" wherein all data that the server must 
have to execute a search is sent to the server, with the exception of 
the search string itself. 
The dialog procedure retrieves the current values of the search 
parameters through calls to GetSearchParms, 
GetTransactionTimeout, and GetServerNode. GetSearchParms 
retrieves the search directory name, search delay value, and a 
bitstring containing flags for continuous searches and transmission 
of pathnames to Program Manager. GetTransactionTimeout 
retrieves the current transaction time-out value. GetServerNode 
retrieves the NetDDE node name of the machine on which the 
demonstration server runs. If the server is running on the same 
machine as the client, no node name is used (the server node name 
is a string of length zero). Further information on the configuration 
data can be found in the Client And Server Features section of this 
article. 
When the configuration parameters are saved, the node name 
must be saved first using SetServerNode so that the configuration 
conversation with the server may be started. The configuration 
conversation is then started using DoDDESearchConfigure. If the 
configuration conversation fails, the node name may be in error, 
and we revert to the previously known node name by calling 
RevertServerNode. If the configuration conversation succeeds, the 
remaining parameters are saved using SetTransactionTimeout and 
SetSearchParms. This approach guarantees that the local (client) 
copy of the configuration parameters will not be saved before the 
remote (server) copy is saved, thus keeping the client and server 
copies of the data in sync. 

DDEHL.C
The DDEHL.C ("DDE High-Level interface") module starts off 
with small utility functions used to manage time-out values and 
server node names. The majority of this module is composed of 
functions that establish a DDEML conversation, do one specific 
thing, then end the conversation. The macro HandleDDEMLError 
is used throughout this module to standardize and simplify the 
process of DDEML error handling. The macro calls the desired 
function, then calls the error message routine ErrorMsg if needed, 
and optionally executes a return statement with the appropriate 
argument. The definition of this macro may be found in 
COMMDEFS.H. 
DoDDESystemTopic is a generic interface into the server's 
system topic functionality. This function is called directly from the 
WM_COMMAND handler for our top-level window process. 
DoDDESystemTopic retrieves the appropriate data from the server 
through an XTYP_REQUEST transaction and displays the data in 
a message box. This function demonstrates a standard procedure 
for one-transaction conversations: establish the conversation 
(BeginDDEConversation), create an appropriate item string handle 
(DdeCreateStringHandle), execute the transaction 
(DoTransaction), free the string handle (DdeFreeStringHandle), 
and end the conversation (EndDDEConversation). The retrieved 
data can then be processed as appropriate. We used 
DdeAccessData to gain read-only access to our copy of the data, 
and then displayed the data. Access to the data was then 
relinquished using DdeUnaccessData, and the data was freed 
using DdeFreeDataHandle (remember that it is necessary to 
explicitly free data returned from synchronous transactions). 
DoDdeSearchConfigure implements the configuration 
conversation with the server and follows the "one transaction 
conversation" model established within DoDDESystemTopic. After 
creating an item string handle, DoDdeSearchConfigure formats the 
configuration data, creates a DDEML data handle for the 
configuration data, and transmits the data to the server using an 
XTYP_POKE transaction. 
BeginDDESearchFind calls BeginDDESearchConv to start a 
conversation if one does not already exist, and then start an advise 
loop on the FIND item. This empowers the server to send us a 
found filename at any time. The remainder of 
BeginDDESearchFind XTYP_POKEs the string we'd like to find 
into the server. EndDDESearchFind ends the advise loop 
previously established by BeginDDESearchConv. 
ProcessFoundFile, which is called in response to the receipt of an 
XTYP_ADVDATA transaction, sends an MM_FILE_FOUND 
message to the client's top-level window. This causes the pathname 
of the found file to be displayed within the DDEML client 
application's client area. If the user checked the "Xmit to Program 
Manager" checkbox in the search configuration dialog, a 
conversation with Program Manager is started so that the "Files 
Found" group may be created if necessary. If the group already 
exists, we search it to see if it already contains an icon for the file 
we have found. The group is then shown, and if no icon exists for 
the found filename, an icon is created and displayed within the 
group window. All Program Manager functionality is delivered via 
XTYP_EXECUTE transactions. For further information on the 
Program Manager DDE interface, consult the Microsoft Windows 
Programmer's Reference, Volume 1: Overview. 

DDE.C
The demonstration client's DDEML callback (DdeClientCallback) 
processes the found filenames that are sent to the client as advise 
data. Although the demo client does not use asynchronous 
transactions, DdeClientCallback nevertheless includes code 
invoked on receipt of the XTYP_XACT_COMPLETE transaction 
that shows how this transaction might be processed. 
InitTopicsAndItems creates string handles of use to the client 
application. CleanupTopicsAndItems frees these string handles. 
BeginDDEConversation starts individual DDE conversations by 
calling DdeConnect and takes care of the required string handle 
management. EndDDEConversation does little more than call 
DdeDisconnect. DoTransaction calls DdeClientTransaction, and 
performs some parameter checking to insure that 
DdeClientTransaction is given a consistent parameter list. 
BeginDDEConversation, EndDDEConversation, and 
DoTransaction all retrieve an error index, if required. 
DoGetData is a utility function used to copy transaction data to 
locally managed buffers. GetSearchConvHandle and 
SetSearchConvHandle are supplied to allow other modules to 
query and store the advise loop's conversation handle. 

A Tour Through the Demo Server
The WinMain function belonging the the demonstration server is 
found within the source file DEMOSER.C. WinMain implements a 
PeekMessage-based loop to enable the server to perform processing 
"at the bottom of the loop"that is, after a given message is pulled 
from the queue, translated and dispatched, WinMain regains 
control so that it may periodically perform work. The work to be 
done is the file search itself, which has been split up into pieces so 
that the search may be interleaved with normal message 
processing, and thus incur minimal impact on system performance. 
The files are searched in 16KB "chunks." 
WM_TIMER messages could have been used instead of 
PeekMessage; the PeekMessage loop approach was used to avoid 
the consumption of system timers, which are limited system 
resources. A tick count is maintained so that the search routine 
(DoSearch) may be invoked every half second rather than after 
each time PeekMessage returns. 
As in the case of the client, the server's InitApp function checks 
for the correct processor mode, and the server's InitInstance 
function calls SetMessageQueue to extend the message queue. 
InitInstance then calls DdeInitialize, creates all needed string 
handles with the server's own InitTopicsAndItems function, 
allocates and locks the buffer into which the "file chunks" will be 
read during text searches, and calls DdeNameService before 
creating the application's top-level window. 
An aside: since the DDEML requires a protected-mode 
environment, Windows may move locked memory blocks as 
required by prevailing system conditions. This allows us to keep 
memory blocks such as the file chunk buffer locked for the duration 
of a Windows-based program without creating "memory sandbars," 
which will prevent other programs from allocating and locking 
memory. 
Getting back to our code walk-through, the server's Cleanup 
function unregisters the server's service name by calling 
DdeNameService, and performs all GLOBALHANDLE and HSZ 
cleanup before calling DdeUninitialize. Although DdeUninitialize 
will implicitly free all string handles created for the specified 
DDEML application-instance, we should explicitly free these 
handles within our programs. 
The message processing within the server's WndProc function 
has nothing whatsoever to do with the DDEML. WndProc's 
WM_CREATE processing creates a list box that fills the client area 
of the application's top-level window. Although this list box is filled 
with the pathnames of files found by the server application, one 
may not browse a file within the server as one does within the 
client. Furthermore, the server displays the pathname of a found 
file each time search text is found within that file; the client never 
displays more than one copy of the found file's pathname. 

DDE.C
The demonstration server's DDEML callback 
(DDEServerCallback) implements a table-driven server using the 
two-level table scheme previously discussed. Functions whose 
addresses are stored in the item-level tables pass the complete 
callback parameter list to the helper function, along with one 
additional argument provided in the event that the callback finds it 
convenient to pass the helper function additional data. Callback 
helper functions are referred to as "server action" functions, and 
the names of such functions are prefixed with "SA" to identify them 
as such. 
The topic and format of an incoming transaction is validated by 
CheckTopicAndFormat. The XTYP_CONNECT, 
XTYP_WILDCONNECT and XTYP_ERROR transactions are 
handled in their entirety within DDEServerCallback's switch 
statement. The XTYP_CONNECT code checks the conversation 
context for reasonable values using IsMatchingConvContext, and 
passes or fails the conversation on that basis alone. The 
XTYP_WILDCONNECT processing makes certain that the service 
name (passed within the callback's hsz2 string handle argument) is 
correct, and then calls IsMatchingConvContext. If both of these 
tests pass, a NULL-terminated array of HSZPAIRs is created using 
DdeCreateDataHandle and expanded with DdeAddData. This 
array contains string handles for all topics supported by the server 
application paired with the string handle for the server's service 
name. XTYP_ERROR is processed by displaying an error message, 
abandoning all active asynchronous transactions, and closing the 
server application. 
The processing for the XTYP_REQUEST transaction illustrates 
our mechanism for passing extra data to the callback helper 
function. Our example uses the extra helper function argument to 
tell the function that creates the list of supported topics (SATopics) 
that the System topic itself should not appear in the list. 
After the switch statement's transaction processing is complete, 
the transaction's topic is looked up within the topic table. When the 
correct topic is found, the item table pointer within that topic's 
structure is used to look up the desired item. When that item is 
found, the associated helper function is called and its return value 
is translated into an appropriate value according to the 
transaction's return type (otherwise known as its XCLASS). 
IsMatchingConvContext implements the validation of the 
transaction's CONVCONTEXT structure members as discussed 
within the explanation of XTYP_CONNECT's processing. 
The functions SATopics, SAItems, SARtnMsg, SAStatus, 
SAFormats, and SAHelp are helper functions for the required 
system topic items. These are "boilerplate" functions that are 
useable almost as-is within other DDEML server applications (for 
example, SAHelp's wsprintf call would need to be modified as 
appropriate). 
The helper function for the configuration conversation 
(SAConfigure) supports the XTYP_POKE transaction. At first, the 
transaction data is copied into a local buffer using DdeGetData. 
SAConfigure then breaks out search parameters from the 
transmitted string data and stores the values within the "search 
engine." 
The helper function for text searches (SAFind) is a bit more 
complex than the other helper functions, and supports four 
transactions. Within SAFind, the XTYP_ADVSTART and 
XTYP_ADVSTOP transactions are instantly approved. Since the 
XTYP_POKE transaction is used to send the desired search text 
from client to server, SAFind's handling of that transaction copies 
the search string to a local buffer using DdeGetData, then passes 
the string into the search engine using SetSearchString. SAFind 
starts the search engine by calling InitSearch after the string is 
successfully stored. 
The search engine generates XTYP_ADVREQ transactions that 
are sent to SAFind for processing. SAFind calls 
GetFoundFileName to obtain the pathname of the found file, 
displays a message within the list box that fills the server 
application's client area, and returns a data handle representing 
the pathname to the server's callback. The data handle is created 
with a call to DdeCreateDataHandle, and will ultimately be passed 
back to the client within its DDEML callback function. 

FIND.C
SetSearchString maintains an array of structures, each of which 
holds a search string and a handle for the conversation that 
brought the search string to the server. SetSearchString copies the 
search string and conversation handle to the first empty element 
within the array. Since the array has a fixed length, 
SetSearchString will return FALSE if it cannot store any more 
search information; otherwise, it returns TRUE. 
ResetSearch resets different parts of the search engine. 
SetSearchInfo stores the XTYP_POKE transaction data sent 
during the configure conversation. InitSearch conducts the ugly 
business of setting up a pathname with optional wildcards so that 
the search engine will know which files to search. InitSearch also 
prepares a buffer that will contain the full pathname of each file 
searched. 
As one might guess from the name, DoSearch is the function that 
actually performs the search. It is called within the server's 
message loop way back in WinMain. DoSearch maintains the name 
of the file currently being searched, along with the offset within 
that file where the next read will begin. The filename is 
periodically updated using the DOS FindFirst/FindNext services so 
that wildcards may be applied to find multiple files within a given 
directory. When a file has been completely read, the offset is reset 
(assigned 0) and the next filename is gotten. 
SearchChunk performs the job of opening the file, seeking to the 
desired offset, reading the file, and searching through its data for 
the specified search string(s). When the specified search text has 
been found within the file's data, SetFoundFileName is called to 
store the current filename within a dynamically allocated array 
along with the handle of the conversation that submitted the text. 
The conversation handle is obtained from the array of structures 
maintained by SetSearchString. DdePostAdvise is then called to 
post an XTYP_ADVREQ transaction to the server callback (this 
transaction is ultimately processed within SAFind). The server 
callback may then grab the filename using GetFoundFileName. In 
addition to returning the name of the found file, 
GetFoundFileName also marks the dynamically allocated array 
element containing that filename as available for reuse. 

Conclusion
All of these factors have put client-server terminology into a state 
of flux. The clarification of client-server definitions lays the 
foundation for client-server technologies such as DDE, which may 
then be used to accomplish the business-driven imperatives of 
faster application development, better performance and lower 
maintenance costs. 
DDE has long been the king of Windows-based interapplication 
communication. DDE's power and flexibility has given it a secure 
place within the list of features expected of state-of-the-art and 
mundane Windows-based applications alike. If DDE is the king, 
DDEML is the heir apparent to the throne. Although OLE might 
be the heir presumptive, several factors argue heavily in favor of 
DDEML's use. 
The ability to quickly implement robust and consistent client-
server-based applications at the level of the workstation speaks to 
the convenience of DDEML implementation. The extensibility 
through NetDDE that allows DDEML applications to interoperate 
with heterogenous network-based platforms speaks to the 
flexibility and power of DDEML-based solutions. Historic 
compatibility with DDE-aware applications and the ability to 
leverage future enhancements through the use of a standard API 
ties DDEML to both past and future. By providing this wealth of 
features, DDEML provides the superior client-server environment 
of the present. 


