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.