However, even if you do not have all the prerequisites, you might find the tutorial useful to get an idea of what an ORBLink application entails.
Because of the length of this tutorial, you may find it more convenient to print out rather than to read on-line.
We will assume that there are two distinct Lisp worlds that have been started: one is called the server and the other the client.
Commands that should be typed into the server or client listener are
prefaced with [server-listener]USER>
or
[client-listener]USER>
in the text below.
In broad terms, to start our application we will:
Our tasks are thus logically partitioned into two categories: server-side and client-side.
These method invocations will result in the forwarding of remote requests to the server object.
It is important to note that either the client or the server side can be implemented in any CORBA-compliant language; that the client or server process can reside on the same or on a different computer in numerous platforms; and that the overall structure of these steps is the same for any language/platform configuration.
After these basic steps we will show how a Lisp server or client can be modified dynamically.
Let us, thus, begin by starting the server side:
The Lisp source code for this example is included in the directory examples/grid/cl/.
You can load the example code from any directory. However, for specificity we will assume here that the current working directory is the root of the ORBLink home installation. You can use the :cd
from within a Lisp listener to set the current working directory of the listener, e.g.
[server-listener]USER> :cd /usr/acl-5/code/orblink [server-listener]USER> (require :orblink)
We recommend as well setting the current package to user
:
:package :user
module example { interface grid; interface grid { readonly attribute short height; readonly attribute short width; void set( in short n, in short m, in string value ); string get( in short n, in short m ); }; };
To compile the IDL, give the pathname of the IDL as the argument to the IDL compiler:
[server-listener]USER> (corba:idl "examples/grid/idl/grid.idl")This will define the classes
example:grid
, example:grid-proxy
, and
example:grid-servant
. The corba:idl
function will return an Interface Repository object that
encapsulates in CORBA compliant format the definitions in the IDL file.
The class example:grid-servant
already has defined slots named op:width
and
op:height
, corresponding to the attributes in the IDL definition of the grid
interface. These slots have pre-defined readers
named op:width
and op:height
with corresponding initialization arguments
:width
and :height
.
grid
interface, it is first necessary to define a class
that extends example:grid-servant
(technically this step is unnecessary, insofar as the new methods could
be defined on the example:grid-servant
class directly, but this usage would be poor style.
We will name our user-defined class, which extends example:grid-servant
, user::grid-implementation
.
Our class user::grid-implementation
will extend example:grid-servant
and will include a single
extra slot named array
that holds the actual values in the grid.
The example:grid-servant
class defines slots named op:width
and op:height
and, since these are readonly
attributes, it defines readers of the same name. Our grid-implementation
will add default initforms to these slots
of values 4
and 5
respectively. Users who wish to initialize the grid with a different size can do so using the
automatically defined :width
and :height
initargs.
Our class definition thus looks like this (or see the file examples/grid/cl/grid-implementation.cl):
(defclass grid-implementation (example:grid-servant) ( (op:width :initform 4) (op:height :initform 5) (array)))We define an initializer for this class that simply initializes the array to the size specified by the values of the slots named
op:width
and op:height
, with initial element the string "initial"
:
(defmethod initialize-instance :after ((this grid-implementation) &rest args) (setf (slot-value this 'array) (make-array `(,(op:width this) ,(op:height this)) :initial-element "Initial")))
Finally, we implement the IDL operations named get
and set
. Because these are IDL operations,
their implementation must be via the corba:define-method
macro. The syntax
of corba:define-method
is specified as part of the CORBA IDL/Lisp mapping and closely follows
the syntax of the usual defmethod
macro.
For example the get
method is implemented as:
(corba:define-method get ((this grid-implementation) row column) (aref (slot-value this 'array) row column))
You should now load the file that contains the definitions of the grid implementation class and its associatiated methods:
[server-listener]USER> :ld examples/grid/cl/grid-implementation.cl
user::grid-implementation
:
[server-listener]USER> (setq test-grid (make-instance 'grid-implementation)) [server-listener]USER> (op:set test-grid 1 2 "This is a test.") [server-listener]USER> (op:get test-grid 1 2) ---> "This is a test"
(Note that not all responses from the Lisp listeners are printed here).
The ORB itself is only involved implicitly in this
computation; there is no marshalling or unmarshalling, and no socket
connections, involved. The op:get
and op:set
methods are normal CLOS
methods.
test-grid
remotely, it is
necessary to publish the IOR (interoperable object reference) of
test-grid
.
The IOR of the test-grid
object can be obtained by invoking the
op:object_to_string
method on the ORB itself. The ORB is always bound
to corba:orb
:
USER> (op:object_to_string corba:orb test-grid) ---> [a long string of characters beginning with "IOR"]As a side effect of computing this string, called a stringified IOR, a TCP socket listener has been started that waits from invocations on the
test-grid
object.
Since no client yet knows the IOR of test-grid
, however, no invocations can be forthcoming until
we make this IOR available to a client.
The simplest way to make the IOR available is for the server to write the IOR to a file that is then
read by the client. This method is not particularly general, of course, but it will suffice to run simple
examples. Choose a file for storing the IOR that is both writeable by the server and that
can be read by any client. For example, you can try using the filename [directory]/grid.ior
where the string
"[directory]" in the following should be replaced by some directory to which you have write access:
USER> (orblink:write-ior-to-file test-grid "[directory]/grid.ior")You should verify now that the IOR string you computed above has indeed been written to the file
[directory]/grid.ior
. Note that you can examine the source
to the write-ior-to-file
function in the file examples/ior-io/cl/sample-ior-io.cl:
(defun orblink:write-ior-to-file (object pathname) "Writes the IOR of object, the first argument, to the file denoted by pathname, the second argument. Because this routine is primary explanatory, little error checking is performed. If *default-ior-directory* is non-nil, pathname is first merged with *default-ior-directory*" (when *default-ior-directory* (setq pathname (merge-pathnames pathname *default-ior-directory*))) (ensure-directories-exist pathname) ; Create intermediate directories if necessary (with-open-file (stream pathname :direction :output :if-exists :supersede) (format stream ("~A" (op:object_to_string corba:orb object))) (format t "Wrote ior to file: ~a~%" pathname) (force-output) t))
test-grid
object has been published in a "well-known" place, a client
can bind to it. You should start a new Lisp world for this portion of the tutorial, perhaps
on a different machine, and then restart ORBLink and recompile the file examples/grid/idl/grid.idl
.
Thus there are now two Lisp listeners: the client and the server. (In fact this example will work just as well if the client and
server are implemented in the same image and the same listener, but it is clearer for the exposition to separate them).
[client-listener]USER> (require :orblink) [client-listener]USER> (corba:idl "examples/grid/idl/grid.idl")
Since the IOR now resides in the file [directory]/grid.ior
, it may be read simply via:
[client-listener]USER> (setq ior (with-open-file (stream "[directory]/grid.ior" :direction :input) (read-line stream)))
This form should return, in the client listener, the same long string that was returned above in the server listener
as the result of calling op:object_to_string
on the test-grid
object.
The next step is to create a proxy from this IOR. This can be done using the CORBA compliant
string_to_object
operation on the ORB:
[client-listener] USER> (setq test-grid-proxy (op:string_to_object corba:orb ior))
This should return an instance of type example:grid-proxy
. This proxy
may then be used to invoke operation on the server-side object in the server image from the client image.
Note: the preceding two steps, reading an IOR from a file and forming a proxy from that IOR could have been coalesced into the single invocation:
(setq test-grid-proxy (orblink:read-ior-from-file "[directory]/grid.ior))The source code for the
orblink:read-ior-from-file
function is also located in the file
examples/ior-io/cl/sample-ior-io.cl.
test-grid-proxy
object using exactly the
same calling sequence as you did to invoke methods directly on the test-grid
object:
[client-listener]USER> (op:set test-grid-proxy 1 3 "proxy-test") [client-listener]USER> (op:get test-grid-proxy 1 3) ---> "proxy-test"
You can verify from the server world that these values really have changed:
[server-listener]USER> (op:get test-grid 1 3) ----> "proxy-test"
This concludes the first part of the tutorial.
Next, we discuss the issue of modifying the server on the fly.
grid
object with a new
attribute, say name
. Make a copy of the file examples/grid/idl/grid.idl and
modify the copy by adding the line
attribute string name;in the interface definition. Now recompile the modified file using the
corba:idl
function.
For your convenience the modified version is in the file examples/grid/idl/grid-modified.idl and thus the recompilation step is done via:
[server-listener]USER> (corba:idl "examples/grid/idl/grid-modified.idl")
If you now evaluate the form (describe test-grid)
on the server side, you will see that a new
slot named name
has indeed been added to the test-grid
object. Now let's set the value of this new slot:
[server-listener]USER> (setf (op:name test-grid) "Modified grid") [server-listener]USER> (op:name test-grid) ----> "Modified grid"
test-grid
proxy, it also needs to recompile the IDL source:
[client-listener]USER> (corba:idl "examples/grid/idl/grid-modified.idl")
Now the client can invoke the new methods:
[client-listener]USER> (op:name test-grid-proxy) ----> "Modified grid" [client-listener]USER> (setf (op:name test-grid-proxy) "client-modified name") [client-listener]USER> (op:name test-grid-proxy) ----> "client-modified name"
any
handling, and so forth.