| Allegro CL version 6.1 Minimally revised | |||||||
This document contains the following sections:
1.0 IntroductionThe purpose of the RPC utility is to allow separate Lisp images to communicate with each other by remote procedure calls. This is accomplished by:
Although the interface is very symmetric, there is a need to distinguish between the roles of client and server. The server advertises the possibility of a connection, and the client connects to an advertising server. The distinction between client and server is significant when a connection is established (or re-established). Thus, a client image may connect on demand when a call is made, but a server image may call the client only when the client has made a connection. Once the connection is made, the actual calling protocol makes no reference to the distinction.
It is possible for the same image to be both client and server and each image in a set of communicating Lisp images may be both. But remote references to data are specific to a particular connection.
All the symbols associated with RPC reside in the
net.rpc package. RPC functionality is in
the :aclrpc module. It is loaded into an image
by evaluating (require :aclrpc).
For the sake of discussion in the following, we assume that two Lisp images, A and B are communicating. Image A is a server and B is a client.
Image A (server) Image B (client)
(multiple-value-setq (s p l es)
(define-rpc-server 'rpc-socket-server
:home "lispA" :local-port 4326
:open :listener
:connect-action :call
:connect-function
#'(lambda (pt) (setf p pt))
;; :verbose verbose
))
(type-of s)
(null p)
(null es)
(type-of l)
(multiple-value-setq (q ec)
(define-rpc-client 'rpc-socket-port
:home "lispB" :remote-port 4326
:open t
;; :verbose verbose
))
(type-of q)
(null ec)
(rpc-open-p q)
(with-remote-port (q)
(rcall 'print "Hello from B")
)
"Hello from B"
(rpc-open-p p)
(with-remote-port (p)
(rcall 'print "Hello from A")
)
"Hello from A"
(rpc-close q :final t)
(rpc-open-p p)
(rpc-close s :stop :final)
(rpc-open-p :all)
(rpc-open-p :all)
Stream connections are implemented on top of the operating system stream socket implementation. Stream sockets provide reliable two-way communication between two host processes. If a Lisp RPC call is made through a stream socket connection, the caller will receive a value or an error signal indicating that the call failed to complete.
If one Lisp host makes two RPC calls in succession to the same Lisp destination host (on the same stream socket connection), then the remote calls will be begun in the same order as they were sent.
Datagram connections are implemented on top of the operating system datagram socket implementation. The datagram protocol does not guarantee arrival of messages, nor does it preserve order.
Because of these semantics, callbacks hardly ever make sense and remote pointers are of dubious value since any manipulation of a remote pointer usually requires a call back to the home of the reference.
The following classes define the connection parameters and data transmission formats:
rpc-port-server
defines the parameters of an advertising rpc server.
rpc-port defines the
parameters of a connection on both server and client images.
rpc-message defines a
message passed between rpc client and server.
rpc-remote-ref defines
a data item wrapped for transmission.
In the subsections of this section, we describe the Lisp API for Stream Socket connections.
The following classes define the connection parameters of stream socket connections.
rpc-socket-server: this
is the subclass of rpc-port-server specific to stream socket
connections.
rpc-socket-port: this
is the subclass of rpc-port specific to stream socket
connections.
The functions associated with connecting are:
The following operators are used once the port is established.
This RPC API supports three callback models:
Consider two lisp images A and B where A.i and B.j represent threads (Lisp processes) in A and B respectively.
Nested - Nested mode provides a continuity in dynamic environments in each running image.
| Image A -- thread A.i | Image B -- thread B.j |
|
|
|
|
|
|
|
|
|
|
|
|
|
Parallel - Parallel mode may the preferred mode for GUI event callbacks.This mode may be more prone to deadlocks than the nested mode.
| Image A -- thread A.i | Image B -- thread B.j | Image A -- thread A.k | Image B -- thread B.l | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Blocking - Blocking mode prevents recursive callbacks entirely. It requires careful programming to avoid deadlocks or errors.
| Image A -- thread A.i | Image B -- thread B.j | Image A -- thread A.k | |
|
|||
|
|||
|
Each RPC port defines a default callback mode for calls through the port. The caller may override the default mode for an individual call.
In this section and its subsections, we describe the Lisp API for Datagram Socket connections. The functions and methods have the same names as in the Stream Socket API but the semantics are sometimes different because of the nature of the protocol.
In addition to the indeterminate nature of message arrival, datagram connections exhibit several other unique characteristics:
rpc-datagram-server instance.
rpc-datagram-port instance.
Thus datagram connections do not exhibit the same symmetry as stream socket connections.
The following classes define the connection parameters of datagram connections.
rpc-datagram-server:
this is the subclass of rpc-port-server specific to datagram
connections.
rpc-datagram-port:
this is the subclass of rpc-port specific to datagram connections.
The following functions and methods are used for Datagram socket connecting.
rpc-datagram-server, do not specify a
name and make sure the limit
is nil or a large number.
rpc-datagram-server,
this method only has effect if the stop argument
is non-nil, in which case it stops the
listener. When the (required) port argument is an
instance of rpc-datagram-port, the method is
effectively a no-op, since a datagram port is closed after each RPC
message.
rpc-datagram-server
instance or the name of one. The server instance is used for incoming
messages and the client instance is used for outgoing messages.
The following functions and methods provide the interface to datagram socket connections.
The following macros and function allow defining remote functions, methods, and classes.
As said in Section 3.0 Introduction to stream and datagram socket connections, the datagram protocol does not guarantee arrival of messages, nor does it preserve the order of messages. Under these semantics, callbacks hardly ever make sense and remote pointers are of dubious value since any manipulation of a remote pointer usually requires a call back to the home of the reference.
Therefore, the datagram interface forces the callback mode to
:blocking. Any mode arguments
are ignored. Here is the description of blocking mode repeated from
Section 4.4 Callback style of stream sockets.
| Image A -- thread A.i | Image B -- thread B.j | Image A -- thread A.k | |
|
|||
|
|||
|
Data is transmitted between two RPC hosts in several ways:
nil
Directly transmitted data is transmitted by value and thus is always copied from one host to the other.
The class rpc-remote-ref (with reader methods rr-home, rr-base, and rr-type) represents remote references in a
Lisp image.
The operators rref and rpc-ref create explicit reference objects.
Remote references are relative to a particular connection. Consider a data item d passed from A to B on connection p as the remote reference r. If B passes r back to A on connection p, then A will see the pointer to d. Consider a second connection q between A and B, and a reference to d is passed to B on q as the remote reference s. If B passes s back to A on connection p, then B will see a remote reference, and not the pointer d.
The arguments to a remote call are passed by the following rules:
nil
Note in addition, any data allocated in memory may be passed by reference.
The operator argument in rcall or rpc-invoke may be one of the following:
(rref symbol :type :function)
where the destination symbol is treated as the name of a function.
(rref t :type :function :symbol-name string)
(rref t :type :function :symbol-name name-of-op)
Any other argument will signal an error in the calling environment.
This section describes the type argument to rref and rpc-ref. The symbol-mode, package-mode, symbol-name, and symbol-package arguments are also described.
The type argument can be any of the following qualifiers:
| Qualifier | Effect |
| :copy-maybe | Create a transfer copy if possible. This is the default behavior for arguments and returned values. |
| :copy | Like :copy-maybe |
| :copy-only | Signal an error if the data cannot be passed by value. |
| :ref-maybe | Create a remote reference if possible. For example, a float is normally passed by value, but may also be passed as a remote reference since it is a stored object in the Lisp image. |
| :ref | Like :ref-maybe |
| :ref-only | Signal an error if the data cannot be passed by reference. This is the case for fixnums, characters, and other immediate Lisp pointers. |
| :ignore | This reference is being created in a call where the value will be ignored. Treated like :copy-maybe. |
| :one-way | This reference is being created in a one-way call. Treated like :copy-maybe. |
The following qualifiers make sense only when wrapping a value explicitly with rref.
| :symbol | Create a remote reference to a symbol. The data argument must be a symbol, but the symbol may be ignored if symbol-name and symbol-package keywords are passed. |
| :value | Create a remote reference to the value of a symbol. |
| :function | Create a remote reference to the function value of a symbol. |
| :class | Create a remote reference to the class named by a symbol. |
| :boolean | Create an immediate boolean value based on data. |
| :null | The data argument must be nil. |
| :string | The data argument must be a string. |
| :int | The data argument must be coercible to an integer. |
| :single | The data argument must be coercible to a single-float value. |
| :double | The data argument must be coercible to a double-float value. |
| :byte | The data argument must be coercible to an integer. It is truncated to a (signed-byte 8) value. |
| :short | The data argument must be coercible to an integer. It is truncated to a (signed-byte 16) value. |
| :char | The data argument must be a character. |
When a symbol reference is created, the symbol-name, symbol-package, symbol-mode and package-mode arguments are used to control how the symbol is found in the remote environment.
The :symbol-mode argument controls how the name of a symbol is passed to the remote site, and how it is decoded there. The default mode is :read-case.
| :symbol-mode | symbol name string | package name sent | package used |
| :read-case | ~S in *package* | *package* | decoded package |
| :absolute | symbol-name | symbol-package | decoded package |
| :read-relative | ~S in *package* | "" (The empty string is sent. Any package argument supplied is ignored.) | *package* |
| :string-equal-one | symbol-name | symbol-package | decoded package |
The :package-mode argument controls how the package name is sent and decoded. The default mode is :read-case.
| :package-mode | string sent | package used |
| :read-case | package-name | Read the package name as a symbol from the passed string and call find-package with the result. |
| :absolute | package-name | Call find-package with the string sent. |
| :string-equal | package-name | Find a package matching the name with string-equal to the string sent. |
| :keyword | "" | The keyword package. |
The default modes are appropriate for communication between two Lisps with similar package layouts and case modes. They may also be suitable for communication from a modern Lisp to an ANSI Lisp. Communication from an ANSI Lisp to a modern Lisp requires some care and the most reliable would be to use explicit symbol-name and symbol-package arguments.
Many RPC functions return a first value of nil if the operation fails. The second value is one
of the following keywords to describe the reason for failure. Some
functions return additional values (indicated by +)
that add information about the
failure.
| Keyword | Meaning and additional information |
:rpc-err-open-server | ropen - cannot call on server port |
:rpc-err-server-timeout |
rpc-open-server - failed to get connection in time |
:rpc-err-not-listening |
rpc-open-server - server is not listening |
:rpc-err-accept + err |
rpc-open-server - error from socket accept |
:rpc-err-connect-action |
connect-action is not :call :process or
nil |
:rpc-err-not-string |
ropen... - home name is not a string |
:rpc-err-version + remote-vers |
ropen... - remote version mismatch |
:rpc-err-duplicate |
ropen... - home name is duplicate in remote host |
:rpc-err-not-found |
ropen...(re-connect) - home name not found |
:rpc-err-limit |
ropen... - remote host has reached connection limit |
:rpc-err-max |
ropen... - remote host saturated (temp) |
:rpc-err-home-busy |
ropen...(connect) - home is already in use (connected or waiting for re-connect) |
:rpc-err-re-connect |
ropen...(re-connect) - home is already in use (connected) |
:rpc-err-refused |
ropen...(re-connect) - refused: bad gen or remote |
:rpc-err-connect + code |
ropen or rpc-open-client...- the first byte received by the server was not one of the two expected values (:connect or :re-connect); code is the actual numeric value of the byte. |
:rpc-err-connected-error + code |
ropen...- failed to get :connected confirmation |
:rpc-err-not-client |
from rpc-open-client |
:rpc-err-not-idle |
from rpc-open-client |
:rpc-err-already-open |
from rpc-open-client |
:rpc-err-streams |
from rpc-open-client - stream not consistent |
:rpc-err-connect-failed + err |
ropen... - connect failed, err from remote host |
:rpc-err-connect-failed + rhome |
ropen... - connect failed, duplicate connect attempt |
:rpc-err-connect-failed +
sockerr |
ropen... - connect failed with sockerr |
:rpc-err-connect-failed + long |
ropen... - connect failed after n attempts |
:rpc-err-re-connect-failed + rhome |
ropen... - duplicate re-connect attempt |
:rpc-err-connerr + err |
ropen... - connect failed, err from remote host |
:rpc-err-bad-connect + code |
ropen... - protocol garbled (expected :confirm-connect or :conn-error) |
:rpc-err-begin-failed
+ begin-err... |
rpc-open-server |
:rpc-err-begin-state
+ state |
rpc-begin |
:rpc-err-begin-gate |
|
:rpc-err-begin-fail
+ state + flag |
|
:rpc-err-begin-timeout
+ port |
|
:rpc-err-req-dest |
rpc-send-request - destination thread unknown |
:rpc-err-invoke-dest |
rcall... - destination thread unknown |
:rpc-err-user + ~A |
request/invoke - error in remote application code |
:rpc-err-socket-error +
sockerr | rpc-open-server - error from socket call |
Locking and synchronization are major concerns in a distributed application. The following macros and functions extend the usefulness of multiprocessing gate objects (see Gates (both models) in multiprocessing.htm).
We include two simple examples of RPC coding in examples/aclrpc/ subdirectory of the Allegro directory. The file rpc-nameserve.cl contains a simple nameserver implemented with datagram calls.
The file rpc-inspect.cl contains an extension to the ACL console inspector. The extension uses RPC calls to display the components and contents of remote data objects.
The server image is activated by evaluating
(server) and the client image is activated by
evaluating (client).
The client function sets the variable *var*
in the server image. The variable is set to a remote reference to a
list of objects in the client image.
The following annotated typescript shows how the variable
*var* is examined from the console of the server
image.
cl-user(23): *var* #<rpc-ref inspectee:pointer#161529357/1001/"cons"> cl-user(24): :i * rpc-remote-ref @ #x20e518aa = #<rpc-ref inspectee:pointer#161529357/1001/"cons"> 0 RefType ------> The symbol :pointer 1 RemoteHome ---> A simple-string (9) "inspectee" 2 RemoteType ---> A simple-string (4) "cons" 3 RemoteParts --> #<remote-field-defs @ #x20e5c02a>
As we inspect the remote reference in *var*, the
inspector extension shows the information that is available without
leaving the local image. This is a design decision in the inspector
extension - remote information is retrieved only on demand to avoid
potentially extensive delays.
cl-user(25): :i 3 remote-field-defs @ #x20e5c1e2 = #<remote-field-defs @ #x20e5c1e2> 0 RemoteObject -> #<rpc-ref inspectee:pointer#161529357/1001/"cons"> 1 Size ---------> fixnum 5 [#x00000014] 2 LookingAt ----> (0 4), a proper list with 2 elements 3 nil ----------> #<rpc-ref inspectee:pointer#161529357/1002/"myclass"> 4 nil ----------> #<rpc-ref inspectee:pointer#161529357/1003/"mystruct"> 5 nil ----------> #<rpc-ref inspectee:pointer#161529357/1004/"t"> 6 nil ----------> #<rpc-ref inspectee:pointer#161529357/1005/"t"> 7 nil ----------> (1 2 3 4 . 5), a dotted list with 4 elements
If we select the RemoteParts field, the inspector goes to the inspectee image and retrieves the top-level components of the list. Line 1 shows that we are looking at a remote object with 5 components. Line 2 shows that we are looking at components 0 through 4, all the components in this case.
cl-user(26): :i 3 rpc-remote-ref @ #x20e60a52 = #<rpc-ref inspectee:pointer#161529357/1006/"myclass"> 0 RefType ------> The symbol :pointer 1 RemoteHome ---> A simple-string (9) "inspectee" 2 RemoteType ---> A simple-string (7) "myclass" 3 RemoteParts --> #<remote-field-defs @ #x20e60eca>
As we select line 3 (or component 0), we again see a remote reference.
cl-user(27): :i 3 remote-field-defs @ #x20e61042 = #<remote-field-defs @ #x20e61042> 0 RemoteObject -> #<rpc-ref inspectee:pointer#161529357/1006/"myclass"> 1 Size ---------> fixnum 2 [#x00000008] 2 LookingAt ----> (0 1), a proper list with 2 elements 3 a ------------> fixnum 123 [#x000001ec] 4 b ------------> fixnum 455 [#x0000071c]
When we select the RemoteParts field, we see the slot names and the values.
cl-user(28): :i - rpc-remote-ref @ #x20e60a52 = #<rpc-ref inspectee:pointer#161529357/1006/"myclass"> 0 RefType ------> The symbol :pointer 1 RemoteHome ---> A simple-string (9) "inspectee" 2 RemoteType ---> A simple-string (7) "myclass" 3 RemoteParts --> #<remote-field-defs @ #x20e62eb2> cl-user(29): :i - remote-field-defs @ #x20e5c1e2 = #<remote-field-defs @ #x20e5c1e2> 0 RemoteObject -> #<rpc-ref inspectee:pointer#161529357/1001/"cons"> 1 Size ---------> fixnum 5 [#x00000014] 2 LookingAt ----> (0 4), a proper list with 2 elements 3 nil ----------> #<rpc-ref inspectee:pointer#161529357/1007/"myclass"> 4 nil ----------> #<rpc-ref inspectee:pointer#161529357/1008/"mystruct"> 5 nil ----------> #<rpc-ref inspectee:pointer#161529357/1009/"t"> 6 nil ----------> #<rpc-ref inspectee:pointer#161529357/1010/"t"> 7 nil ----------> (1 2 3 4 . 5), a dotted list with 4 elements cl-user(30): :i 4 rpc-remote-ref @ #x20e6698a = #<rpc-ref inspectee:pointer#161529357/1011/"mystruct"> 0 RefType ------> The symbol :pointer 1 RemoteHome ---> A simple-string (9) "inspectee" 2 RemoteType ---> A simple-string (8) "mystruct" 3 RemoteParts --> #<remote-field-defs @ #x20e66e3a>
Returning to the initial object contents, we select line 4, or element 1 of the remote list.
cl-user(31): :i 3 remote-field-defs @ #x20e66fb2 = #<remote-field-defs @ #x20e66fb2> 0 RemoteObject -> #<rpc-ref inspectee:pointer#161529357/1011/"mystruct"> 1 Size ---------> fixnum 16 [#x00000040] 2 LookingAt ----> (0 9), a proper list with 2 elements 3 a ------------> The symbol nil 4 b ------------> The symbol nil 5 c ------------> The symbol nil 6 d ------------> The symbol nil 7 e ------------> The symbol nil 8 f ------------> The symbol nil 9 g ------------> The symbol nil 10 h ------------> The symbol nil 11 i ------------> The symbol nil 12 j ------------> The symbol nil 13 MoreParts ----> #<remote-field-defs @ #x20e71622>
As we select the RemoteParts field, we see the first 10 slots of a structure with 16 slots. We can see the remaining slots by selecting the MoreParts field on line 13.
cl-user(32): :i - rpc-remote-ref @ #x20e6698a = #<rpc-ref inspectee:pointer#161529357/1011/"mystruct"> 0 RefType ------> The symbol :pointer 1 RemoteHome ---> A simple-string (9) "inspectee" 2 RemoteType ---> A simple-string (8) "mystruct" 3 RemoteParts --> #<remote-field-defs @ #x20e71842> cl-user(33): :i - remote-field-defs @ #x20e5c1e2 = #<remote-field-defs @ #x20e5c1e2> 0 RemoteObject -> #<rpc-ref inspectee:pointer#161529357/1001/"cons"> 1 Size ---------> fixnum 5 [#x00000014] 2 LookingAt ----> (0 4), a proper list with 2 elements 3 nil ----------> #<rpc-ref inspectee:pointer#161529357/1012/"myclass"> 4 nil ----------> #<rpc-ref inspectee:pointer#161529357/1013/"mystruct"> 5 nil ----------> #<rpc-ref inspectee:pointer#161529357/1014/"t"> 6 nil ----------> #<rpc-ref inspectee:pointer#161529357/1015/"t"> 7 nil ----------> (1 2 3 4 . 5), a dotted list with 4 elements cl-user(34): :i 5 rpc-remote-ref @ #x20e75572 = #<rpc-ref inspectee:pointer#161529357/1016/"t"> 0 RefType ------> The symbol :pointer 1 RemoteHome ---> A simple-string (9) "inspectee" 2 RemoteType ---> A simple-string (14) "(array t (57))" 3 RemoteParts --> #<remote-field-defs @ #x20e75a6a> cl-user(35): :i 3 remote-field-defs @ #x20e75be2 = #<remote-field-defs @ #x20e75be2> 0 RemoteObject -> #<rpc-ref inspectee:pointer#161529357/1016/"t"> 1 Size ---------> fixnum 57 [#x000000e4] 2 LookingAt ----> (0 9), a proper list with 2 elements 3 nil ----------> #<rpc-ref inspectee:pointer#161529357/1017/"cons"> 4 nil ----------> #<rpc-ref inspectee:pointer#161529357/1018/"cons"> 5 nil ----------> #<rpc-ref inspectee:pointer#161529357/1019/"cons"> 6 nil ----------> #<rpc-ref inspectee:pointer#161529357/1020/"cons"> 7 nil ----------> #<rpc-ref inspectee:pointer#161529357/1021/"cons"> 8 nil ----------> #<rpc-ref inspectee:pointer#161529357/1022/"cons"> 9 nil ----------> #<rpc-ref inspectee:pointer#161529357/1023/"cons"> 10 nil ----------> #<rpc-ref inspectee:pointer#161529357/1024/"cons"> 11 nil ----------> #<rpc-ref inspectee:pointer#161529357/1025/"cons"> 12 nil ----------> #<rpc-ref inspectee:pointer#161529357/1026/"cons"> 13 MoreParts ----> #<remote-field-defs @ #x20e7cf1a> cl-user(36): :i 13 remote-field-defs @ #x20e7d0a2 = #<remote-field-defs @ #x20e7d0a2> 0 RemoteObject -> #<rpc-ref inspectee:pointer#161529357/1016/"t"> 1 Size ---------> fixnum 57 [#x000000e4] 2 LookingAt ----> (10 19), a proper list with 2 elements 3 nil ----------> #<rpc-ref inspectee:pointer#161529357/1027/"cons"> 4 nil ----------> #<rpc-ref inspectee:pointer#161529357/1028/"cons"> 5 nil ----------> #<rpc-ref inspectee:pointer#161529357/1029/"cons"> 6 nil ----------> #<rpc-ref inspectee:pointer#161529357/1030/"cons"> 7 nil ----------> #<rpc-ref inspectee:pointer#161529357/1031/"cons"> 8 nil ----------> #<rpc-ref inspectee:pointer#161529357/1032/"cons"> 9 nil ----------> #<rpc-ref inspectee:pointer#161529357/1033/"cons"> 10 nil ----------> #<rpc-ref inspectee:pointer#161529357/1034/"cons"> 11 nil ----------> #<rpc-ref inspectee:pointer#161529357/1035/"cons"> 12 nil ----------> #<rpc-ref inspectee:pointer#161529357/1036/"cons"> 13 MoreParts ----> #<remote-field-defs @ #x20e843ea> cl-user(37): :i 13 remote-field-defs @ #x20e84572 = #<remote-field-defs @ #x20e84572> 0 RemoteObject -> #<rpc-ref inspectee:pointer#161529357/1016/"t"> 1 Size ---------> fixnum 57 [#x000000e4] 2 LookingAt ----> (20 29), a proper list with 2 elements 3 nil ----------> #<rpc-ref inspectee:pointer#161529357/1037/"cons"> 4 nil ----------> #<rpc-ref inspectee:pointer#161529357/1038/"cons"> 5 nil ----------> #<rpc-ref inspectee:pointer#161529357/1039/"cons"> 6 nil ----------> #<rpc-ref inspectee:pointer#161529357/1040/"cons"> 7 nil ----------> #<rpc-ref inspectee:pointer#161529357/1041/"cons"> 8 nil ----------> #<rpc-ref inspectee:pointer#161529357/1042/"cons"> 9 nil ----------> #<rpc-ref inspectee:pointer#161529357/1043/"cons"> 10 nil ----------> #<rpc-ref inspectee:pointer#161529357/1044/"cons"> 11 nil ----------> #<rpc-ref inspectee:pointer#161529357/1045/"cons"> 12 nil ----------> #<rpc-ref inspectee:pointer#161529357/1046/"cons"> 13 MoreParts ----> #<remote-field-defs @ #x20e8b8ba>
To see the components of a very large object, we need to select the MoreParts field several times.
cl-user(38): :i 0 rpc-remote-ref @ #x20e75572 = #<rpc-ref inspectee:pointer#161529357/1016/"t"> 0 RefType ------> The symbol :pointer 1 RemoteHome ---> A simple-string (9) "inspectee" 2 RemoteType ---> A simple-string (14) "(array t (57))" 3 RemoteParts --> #<remote-field-defs @ #x20e8bc82>
We can return to the initial reference of the large object by selecting the RemoteObject field on line 0. This avoids the need to climb back through multiple MoreParts steps.
Copyright (c) 1998-2001, Franz Inc. Berkeley, CA., USA. All rights reserved.
Documentation for Allegro CL version 6.1 update # 1. This page has had minimal revisions.
Created 2001.12.15.
| Allegro CL version 6.1 Minimally revised | |||||||