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.
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 IBM 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 40.
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."
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.
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.
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.
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.
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.
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.