dde.lsp
;
an older version is in the distribution of version 3.52.12.
<dde.lsp>= (in-package "SYSTEM") (export '(dde-request dde-poke dde-execute)) <dde client functions> <server support variables> <server support functions> <standard server objects> <standard conversation objects> <initializing the standard server> <command line conversation objects>
The function dde-execute
taxes a connection identifier, a command
string, and an optional timeout value as a keyword argument and
performs an execute transaction.
<dde client functions>= (<-U) [D->] (defun dde-execute (con cmd &key timeout) (if timeout (dde-client-transaction con :data cmd :timeout timeout) (dde-client-transaction con :data cmd)))
Definesdde-execute
(links are to index).
The function dde-request
takes a connection identifier and an item
string and processes a request transaction. A timeout can be
specified as a keyword argument. The :binary
keyword can be used
to indicate that the result should be a string of all the data
transmitted; otherwise, the results consists of a string terminated by
the first NULL
character.
<dde client functions>+= (<-U) [<-D->] (defun dde-request (con item &key binary timeout) (if timeout (dde-client-transaction con :type :request :item item :binary binary :timeout timeout) (dde-client-transaction con :type :request :item item :binary binary)))
Definesdde-request
(links are to index).
The function dde-poke
takes a connection identifier, an item
string, and a value string and executes a poke transaction. Again, a
timeout can be specified with a keyword argument.
<dde client functions>+= (<-U) [<-D->] (defun dde-poke (con item value &key timeout) (let ((vstring (if (stringp value) value (format nil "~s" value)))) (if timeout (dde-client-transaction con :type :poke :item item :data vstring :timeout timeout) (dde-client-transaction con :type :poke :item item :data vstring))))
Definesdde-poke
(links are to index).
As a simple example, here is a function that does an eval
using DDE:
<dde client functions>+= (<-U) [<-D] (defun dde-eval (e) (let* ((c (dde-connect "XLISP-STAT")) (success (if c (dde-execute c (format nil "~s" e)) nil)) (v (if success (dde-request c "value") nil))) (when c (dde-disconnect c)) (if success (read-from-string v) (error "evaluation failed"))))
Definesdde-eval
(links are to index).
system::dde-server-callback
is defined, it is used for this
purpose. This section describes a version of this function, together
with a support structure, that provide a customizable DDE server. The
details are still experimental and subject to change.
:name
. A string naming the service is returned.
:has-topic topic
. Returns true if the topic named by the
string topic
is supported.
:make-conversation topic
. Returns a new conversation
object for the topic
argument.
:topics
. Returns a list of the topic names that are
supported.
Connections are also assumed to be objects responding to certain messages:
:execute data
. Handles an execute transaction with the
provided data
string. Should return nil
on failure and true
on success.
:request item
. Return a string corresponding to the
requested item
or nil
for failure.
:poke item data
. Process a poke request with data
for
item
. Should return nil
on failure and true on success.
:disconnect
. Called when the client disconnects; can be
used for cleanup operations.
equal
hash on the upper case
service name string. The second table maps objects for handling
individual connections to their HCONV
values provided by the
system.
<server support variables>= (<-U) [D->] (defconstant *dde-servers* (make-hash-table :test 'equal)) (defconstant *dde-conversations* (make-hash-table))
Defines*dde-conversations*
,*dde-servers*
(links are to index).
dde-add-server
adds a server to the server data base. If a
service with the same service name exists it is replaced. Otherwise,
the new service is registered with the DDE system by calling
dde-name-service
with the service name, and, if this call is
successful, is entered in the server data base. There is no mechanism
for removing a service yet.
<server support functions>= (<-U) [D->] ;;**** need to be able to remove service too (defun dde-add-server (server) (let ((service (string-upcase (send server :name))) (old (gethash service *dde-servers*))) (when (or old (dde-name-service service)) (setf (gethash service *dde-servers*) server) t)))
Definesdde-add-server
(links are to index).
The function dde-find-server
looks up the server for the service
name argument in the hash table.
<server support functions>+= (<-U) [<-D->] (defun dde-find-server (name) (values (gethash (string-upcase name) *dde-servers*)))
Definesdde-find-server
(links are to index).
<server support functions>+= (<-U) [<-D->] ;;**** could use a convention about getting back error info from executes (defun dde-server-callback (type fmt hconv hsz1 hsz2 data dw1 dw1) (dde-debug "Server args: ~s~%" (list type fmt hconv hsz1 hsz2 data dw1 dw1)) (ignore-errors (case type (:connect <handle:connect
request>) (:connect-confirm <handle:connect-confirm
request>) (:wildconnect <handle:wildconnect
request>) (t <handle conversation transaction>))))
Connect requests are handled by finding a server and asking the server whether it supports the specified topic.
<handle :connect
request>= (<-U)
(let ((server (gethash hsz2 *dde-servers*)))
(and server (send server :has-topic hsz1)))
Connect-confirm requests are received after a successful connect request. The server is asked to create the connection object, which is then registered under the connection handle.
<handle :connect-confirm
request>= (<-U)
(let* ((server (gethash hsz2 *dde-servers*))
(conv (send server :make-conversation hsz1)))
(setf (gethash hconv *dde-conversations*) conv))
Wildconnect requests are used by the system to ask for information
about supported services. Each server is asked for its list of
topics, a list of service name-topic name lists is constructed and
returned. The internal dde-services
function can be used to query
for available services.
<handle :wildconnect
request>= (<-U)
(let ((val nil))
(flet ((servs (servname server)
(let ((topics (send server :topics)))
(dolist (topic topics)
(push (list servname topic) val)))))
(maphash #'servs *dde-servers*)
val))
Conversation transactions are handled by looking up the conversation object corresponding to the internal conversation handle and sending the object the appropriate message for handling the requested transaction.
<handle conversation transaction>= (<-U) (let ((conv (gethash hconv *dde-conversations*))) (case type (:execute (send conv :execute data)) (:request (send conv :request hsz2)) (:poke (send conv :poke hsz2 data)) (:disconnect (remhash hconv *dde-conversations*) (send conv :disconnect))))
When the variable *dde-debug*
is not nil
, the callback uses
the dde-debug
function to print its call information to debug
output.
<server support variables>+= (<-U) [<-D] (defparameter *dde-debug* nil)
Defines*dde-debug*
(links are to index).
<server support functions>+= (<-U) [<-D] (defun dde-debug (fmt &rest args) (when *dde-debug* (apply #'format *debug-io* fmt args)))
Definesdde-debug
(links are to index).
name
and a topics
slot.
<standard server objects>= (<-U) [D->] (defproto dde-server-proto '(name topics))
Definesdde-server-proto
(links are to index).
The initialization method takes a service name argument and installs
it in the name
slot.
<standard server objects>+= (<-U) [<-D->] (defmeth dde-server-proto :isnew (name) (setf (slot-value 'name) name))
Defines:isnew
(links are to index).
The :name
method returns the contents of the name
slot.
<standard server objects>+= (<-U) [<-D->] (defmeth dde-server-proto :name () (slot-value 'name))
Defines:name
(links are to index).
The topics
slot uses an association list to map topic names to
conversation factories. The :has-topic
method just checks whether
the topic requested has an entry; the :topics
method returns a
list of the association keys. The :add-topic
method adds a topic
and its corresponding factory to the list.
<standard server objects>+= (<-U) [<-D->] (defmeth dde-server-proto :has-topic (topic) (if (assoc topic (slot-value 'topics) :test #'equal) t nil)) (defmeth dde-server-proto :topics () (mapcar #'first (slot-value 'topics))) (defmeth dde-server-proto :add-topic (topic factory) (let* ((topic (string-upcase topic)) (entry (assoc topic (slot-value 'topics) :test #'equal))) (if entry (setf (second entry) factory) (push (list topic factory) (slot-value 'topics)))))
Defines:add-topic
,:has-topic
,:topics
(links are to index).
The :make-conversation
method looks up the topic's conversation
factory and uses it to construct a conversation. If the factory is a
prototype, then it is sent the :new
message with the server and
the topic as arguments. If it is a function, it is called with those
arguments.
<standard server objects>+= (<-U) [<-D] (defmeth dde-server-proto :make-conversation (topic) (let ((confac (second (assoc topic (slot-value 'topics) :test #'equal)))) (if (objectp confac) (send confac :new self topic) (funcall confac self topic))))
<standard conversation objects>= (<-U) [D->] (defproto dde-conversation-proto '(server topic value))
Definesdde-conversation-proto
(links are to index).
The initialization method installs the server and the topic names in their respective slots.
<standard conversation objects>+= (<-U) [<-D->] (defmeth dde-conversation-proto :isnew (server topic) (setf (slot-value 'server) server) (setf (slot-value 'topic) topic))
Defines:isnew
(links are to index).
The :execute
method treats its argument as a sequence of Lisp
expressions in a string. The expressions are evaluated and their
results are stored in the value
slot for retrieval by a subsequent
request transaction. Some clients---Excel in particular---seem to
assume that execute expressions will be enclosed in square brackets.
Following a suggestion of Russell Lenth, to allow this, the
:execute
method uses a read table in which the right and left
square brackets are ignored.
<standard conversation objects>+= (<-U) [<-D->] ;; Using the following modified readtable allows commands to be ;; enclosed in [...]. This seems to be necessary to properly handle ;; execute transactions sent by Excel. (defconstant *dde-readtable* (copy-readtable nil)) (set-macro-character #\[ #'(lambda (x y) (values)) t *dde-readtable*) (set-macro-character #\] #'(lambda (x y) (values)) t *dde-readtable*) (defmeth dde-conversation-proto :execute (cmd) (let ((*readtable* *dde-readtable*) (eof (cons nil nil))) (with-input-from-string (s cmd) (do ((expr (read s nil eof) (read s nil eof))) ((eq expr eof)) (setf (slot-value 'value) (eval expr)))) t))
Defines*dde-readtable*
,:execute
(links are to index).
The standard conversation responds to requests for the VALUE
item
by returning the contents of its value
slot formatted as a string
with prin1
formatting.
<standard conversation objects>+= (<-U) [<-D->] (defmeth dde-conversation-proto :request (item) (when (equal item "VALUE") (format nil "~s" (slot-value 'value))))
Defines:request
(links are to index).
The standard conversation does not accept poke requests and does not do any cleanup on disconnects.
<standard conversation objects>+= (<-U) [<-D] (defmeth dde-conversation-proto :poke (item data) nil) (defmeth dde-conversation-proto :disconnect () nil)
XLISP-STAT
service,
the standard server is just created and installed in the hash table.
<initializing the standard server>= (<-U) (let ((server (send dde-server-proto :new "XLISP-STAT"))) (send server :add-topic "XLISP-STAT" dde-conversation-proto) (send server :add-topic "SYSTEM" dde-conversation-proto) (setf (gethash "XLISP-STAT" system::*dde-servers*) server))
ddeclient
that reads
commands and sends them as DDE execute transactions, but it does not
attempt to retrieve output. To support a DDE command line client we
can define a new topic CMDLINE
that executes commands sent to it
with execute transactions with output redirected to a string. A
request for the VALUE
item retrieves this string.
CMDLINE
conversation objects are created from the prototype
<command line conversation objects>= (<-U) [D->] (defproto cmdline-conversation-proto nil nil dde-conversation-proto)
Definescmdline-conversation-proto
(links are to index).
The initialization method uses the banner
<command line conversation objects>+= (<-U) [<-D->] ;;**** get this internally? (defconstant *banner* (format nil "XLISP-PLUS version 3.04~%~ Portions Copyright (c) 1988, by David Betz.~%~ Modified by Thomas Almy and others.~%~ XLISP-STAT Release ~d.~d.~d.~%~ Copyright (c) 1989-1999, by Luke Tierney.~%" xls-major-release xls-minor-release xls-subminor-release))
Defines*banner*
(links are to index).
and installs it in the value slot for retrieval by the first request
for the VALUE
item. The banner is followed by an initial prompt.
<command line conversation objects>+= (<-U) [<-D->] (defmeth cmdline-conversation-proto :isnew (server topic) (call-next-method server topic) (setf (slot-value 'value) (format nil "~a~%~a" *banner* (make-prompt-string))))
Defines:isnew
(links are to index).
<command line conversation objects>+= (<-U) [<-D->] (defun make-prompt-string () (if (eq (find-package "USER") *package*) "> " (format nil "~a> " (package-name *package*))))
Definesmake-prompt-string
(links are to index).
<command line conversation objects>+= (<-U) [<-D->] (defun read-eval-print-from-string (string) (with-input-from-string (*standard-input* string) (with-output-to-string (*standard-output*) (let ((*debug-io* *standard-output*)) <command line read-eval-print loop>))))
Definesread-eval-print-from-string
(links are to index).
The read-eval-print loop reads commands, evaluates with a check for errors, prints the values or the error condition, and finally prints a prompt. The loop continues until the end of string results in an end of file on the stream.
<command line read-eval-print loop>= (<-U) (let ((eof (cons nil nil))) (do ((expr (read *standard-input* nil eof) (read *standard-input* nil eof))) ((eq expr eof)) (setf +++ ++ ++ + + - - expr) (multiple-value-bind (values error) (ignore-errors (multiple-value-list (eval expr))) (cond (error (format t "~&Error: ~a~%" error)) (t (setf *** ** ** * * (first values)) (format t "~{~&~s~%~}" values)))) (format t "~&~a" (make-prompt-string))))
The conversation :execute
method is then simply
<command line conversation objects>+= (<-U) [<-D->] (defmeth cmdline-conversation-proto :execute (cmd) (setf (slot-value 'value) (read-eval-print-from-string cmd)))
Defines:execute
(links are to index).
The :request
method returns the string in the value
slot and
sets the value
slot to the empty string (**** is this still
necessary?).
<command line conversation objects>+= (<-U) [<-D->] (defmeth cmdline-conversation-proto :request (item) (when (equal item "VALUE") (let ((value (slot-value 'value))) (setf (slot-value 'value) "") value)))
Defines:request
(links are to index).
CMDLINE
topic and its handler need to be added to the
server.
<command line conversation objects>+= (<-U) [<-D] (send (dde-find-server "XLISP-STAT") :add-topic "CMDLINE" cmdline-conversation-proto)
:connect
request>: U1, D2
:connect-confirm
request>: U1, D2
:wildconnect
request>: U1, D2