Discussion of the DdeServerManager
To act as a DDE server, an application creates a DdeServerManager object using the class method name:, which takes a string argument as the name of the new DdeServerManager. This is the name for DDE clients to use in establishing a connection with the server. The application can then register for any of the callbacks listed above for the DdeServerManager. When a DDE client connects to the server, the DdeServerManager creates a new DdeServer object to handle the conversation with that client. Hence, an application only creates one instance of DdeServerManager, but an instance of DdeServer is created for each conversation that the DdeServerManager is managing. The following figure illustrates this setup of runtime DDE objects and applications.
Note:
Applications should never directly create DdeServer objects.
Default database
To offload DDE server event processing from the developer's application to the DdeServerManager, you can create a database of topics and data items for the DdeServerManager, where the data items are stored with their name, format, and current value. Items can be added to the database with the following message to a DdeServerManager:
aDdeServerManager
addItem: itemName
topic: topicName
value: data
format: formatName
Building a database of all topics and data items lets the DdeServerManager perform its default processing in servicing requests from DDE clients, without requiring any intervention from the application. To illustrate, the following code creates a DdeServerManager and builds a database of topics and items that will be serviced.
| aDdeServerManager |
aDdeServerManager := DdeServerManager name: 'Stocks'.
aDdeServerManager
addItem: 'time'
topic: 'Time'
value: Time now printString
format: 'String';
addItem: 'time'
topic: 'Time'
value: (self flatten: Time now)
format: 'Smalltalk Object';
addItem: 'BTRX'
topic: 'Price'
value: 26.50 printString
format: 'String';
addItem: 'EST'
topic: 'Price'
value: 104.56 printString
format: 'String'
The following figure shows an example
DdeServerManager default database after executing the previous code.
The VA Smalltalk Swapper makes it straightforward to use DDE to pass Smalltalk objects back and forth between cooperating Smalltalk applications. The method
flatten: in the above code converts a Smalltalk object into a
ByteArray. The Swapper can also be used on the receiving side of a DDE connection to unflatten the Smalltalk object. The code for doing this is specified in
Converting Smalltalk objects to a ByteArray and back.
The default processing database for an instance of DdeServerManager contains a single server name, one or more topics that are associated with that server name, and one or more items for each topic. An application should only use one instance of a DdeServerManager.
Once a DdeServerManager's database is constructed, the application need only update an item in the database when the data associated with that item changes. The DdeServerManager then automatically takes the appropriate action with any DDE clients linked to that item. For example, if the price of the EST stock changes, then the application runs the following code:
ddeServerManager
updateItem: 'EST'
topic: 'Price'
value: 100.01 printString
format: 'String'
In response to this message, the
DdeServerManager automatically updates any clients that have links or outstanding requests for that data item (assuming that the application does not override the
DdeNrequestCallback). In addition, the
DdeServerManager can automatically respond to DDE client requests for any of the predefined general-interest topics and items listed in
Table 41.
Initiate event
When a DDE client attempts to connect to a DDE server, the DdeNinitiateCallback runs on the server application (assuming the DdeNinitiateCallback is hooked). The DdeCallbackData object passed to the callback contains the topic strings and the name of the server to which the client is trying to connect. If the application wants to accept the connect request, then it must send the following message:
ddeServerManager notifyClientOfSupportFor: topicStr
topicStr is the server application topic string. Note that it is possible for the client to send an empty string for the topic request. In this case, the server application should send the message notifyClientOfSupportFor: for each topic that it supports. It is also possible for the DDE client to use an empty string for the server name. This implies that any server, regardless of name, can respond to the connect request. For example, a server application might implement the DdeNinitiateCallback callback as follows:
initiate: aDdeServerManager
clientData: clientData
callData: callData
"If the application and the topic are empty, then notify the
client about both of the topics."
callData application isEmpty
& callData topic isEmpty ifTrue: [
^aDdeServerManager
notifyClientOfSupportFor: 'Topic1';
notifyClientOfSupportFor: 'Topic2'.
].
"If the application does not match the server name then
reject the connect request."
callData application asLowercase = 'server' ifFalse: [
^self
].
"At this point the application name matches our server name.
Check for a connect request to a topic you support."
callData topic = 'Topic1' ifTrue: [
^aDdeServerManager
notifyClientOfSupportFor: 'Topic1'.
].
callData topic = 'Topic2' ifTrue:
^aDdeServerManager
notifyClientOfSupportFor: 'Topic2'.
].
"There is no topic that you support so reject the connect request."
Note:
The return value was not used. See
Table 47.
If
DdeServerManager default processing is being used for the initiate request (that is, the
DdeNinitiateCallback is not hooked), then the
DdeServerManager checks the server name and the topic names in the default processing database and, if found, creates a connection for the requested topic. However, if the
DdeServerManager receives an empty string for the server name or topic name, it responds by sending
notifyClientOfSupportFor: for each topic in the default processing database. Once connected, a DDE client can request only items associated with the topic specified in the connect request. A client must disconnect and then reconnect to access items in a different topic. The following figure shows how a DDE client initiates a connect request to a DDE server, thereby generating the initiate event.
Warmlink and hotlink event
After the DDE conversation has been established, a DDE client might try to link to a particular data item. When a client attempts a link to a data item, the DdeNwarmlinkCallback or DdeNhotlinkCallback is run on the server application (assuming DdeNwarmlinkCallback or DdeNhotlinkCallback are hooked). In this case, the DdeCallbackData object passed to the callback contains the name of the item, its format, and the current application and topic names. For example, to register for the DdeNwarmlinkCallback, a DDE server application uses:
ddeServerManager
addCallback: DdeNwarmLinkCallback
receiver: self
selector: #warmLink:clientData:callData:
clientData: nil
The callback warmLink:clientData:callData: method looks like this:
warmLink: aDdeServer clientData: clientData callData: callData
"A DDE client has requested a warm link to a piece of data. Check
the name and format and if supported set the return value to true
to acknowledge the connect request. Otherwise set the return value
to false."
| item format |
item := callData item asLowercase.
format := callData format asLowercase.
(item = 'time' and: format = 'string'])
ifFalse: [callData returnValue: false]
ifTrue: [
Transcript show: 'warmLink ', item, ' ', format.
callData returnValue: true.
]
By setting the return value of the DdeCallbackData object to true, the DDE server application has indicated that the item/format is supported. In that case, the DdeServerManager creates a link to that item for the client, and it generates the necessary events back to the DDE client when the data item is updated. The server application must set the return value of the DdeCallbackData object to true if the item/format is supported and to false if the item/format is not supported. A nil return value in the DdeCallbackData object causes the DdeServerManager to look up the item in its default database and, if found, link the client to that item.
Coldlink event
When a DDE client unlinks from a data item, the DdeNcoldlinkCallback is run on the DDE server application (assuming the DdeNcoldlinkCallback is hooked). As with hot- and warmlinking, the DdeCallbackData object passed to the callback contains the name of the item to be unlinked, its format, and the current application and topic names.
If the DDE server application sets the return value of the DdeCallbackData object to true, then the DdeServerManager checks for an existing link to the data item and, if one exists, it unlinks the client from that item. In this case, if a link to the data item does not exist, the DdeServerManager generates an error response back to the client. On the other hand, a false return value in the DdeCallbackData object has no effect on the linkage status of the specified data item.
The DdeNcoldlinkCallback is unique in that a nil return value in the DdeCallbackData object is the same as a true return value.
Request event
When a client wants to retrieve data from a server, the DdeNrequestCallback is run on the server application (assuming the DdeNrequestCallback is hooked). Here the DdeCallbackData object passed to the callback contains the name of the item, its format, and the current application and topic names. If the data is available, the application sets the return value of the DdeCallbackData object to true and sends the message sendItem:value:format: to the DdeServer object passed as the first parameter of the callback. For example, to register for the DdeNrequestCallback, a DDE server application uses:
ddeServerManager
addCallback: DdeNrequestCallback
receiver: self
selector: #request:clientData:callData:
clientData: nil
The callback request:clientData:callData: method looks like:
request: aDdeServer clientData: clientData
"A DDE client is requesting a piece of data. Check that the item and
topic and item are supported. If the item/format is not supported,
set the return value to false. If the item/format is supported then
send the data to the client and set the return value to true."
| item format |
item := ddeCallbackData item asLowercase.
format := ddeCallbackData format asLowercase.
(item = 'time' and: format = 'string'])
ifFalse: [ddeCallbackData returnValue: false]
ifTrue: [
aDdeServer
sendData: ddeCallbackData item
format: 'String'
with: Time now printString.
ddeCallbackData returnValue: true.
]
If nil is set for the return value of the DdeCallbackData object (or if the DdeNrequestCallback is not hooked), the DdeServerManager performs its default processing, which in this case is to check for the item in the default database and if found, send it back to the client.
Run and poke events
When a DDE client wants to send data (called "poke" in DDE parlance) to a server, the DdeNpokeCallback is run on the server application (assuming the DdeNpokeCallback is hooked). The DdeCallbackData object passed to the callback method contains the name of the data item, its format, and the current application and topic names. Server handling of this event is similar to the hot/warm/cold link events.
When a DDE client makes a command execution request on a server, the DdeNexecuteCallback is invoked on the server (again assuming the DdeNexecuteCallback is hooked). Here the DdeCallbackData object passed to the callback method contains the command to be run (as a string), and the current application and topic names. The command string can be accessed from the DdeCallbackData object through the execute message.
Termination event
When a DDE client shuts down or sends a connection termination request to a server, the DdeNterminationCallback method is run on the server application. The server application should not try to disconnect at this point because the client cannot be stopped from terminating the connection. This callback only exists to notify the server that the connection is terminating. Also, the server application should not try to free the DdeServer passed as the first parameter of the callback. The DdeServerManager manages the DdeServer objects for each connection and takes appropriate action as required.