Summary of the IDL/Common Lisp Mapping


This document summarizes highlights and key features of the IDL to Common Lisp mapping for the ORBLink programmer. Each IDL type is mapped to a corresponding Lisp type:

Mapping for primitive types

The mapping for primitive types is straightforward: As a practical matter, this means that you can pass a value of the expected Lisp primitive type as a parameter to any IDL operation declared to accept that type. We will see some more examples below.

Mapping for sequences

An IDL sequence is mapped to a Lisp sequence, that is, to a vector or to a list.

This can cause confusion: when is a vector allowed or required? when is a list allowed or required?

The general rule is: if an IDL operation expects a parameter that is a sequence, either a vector or a list may be passed to that operation.

If an IDL operation returns a sequence parameter, it may be either a vector or a list. ORBLink guarantees that if the invocation was remote then the returned sequence value will be a vector, not a list.

Mapping for interfaces

The most important mapping to know is that of interfaces.

Consider the following interface named I defined in module M:

module M { 
   interface I { attribute long a1;
                 readonly attribute string a2;}
 }
When compiled via corba:idl three classes are created in the Lisp:
  1. A Lisp class named M:I (that is, the symbol named "I" in package M).
  2. A Lisp class named M:I-servant, inheriting from M:I and from corba:servant
  3. A Lisp class named M:I-proxy, inheriting from M:I and from corba:proxy.
Instances of M:I-proxy are proxies: methods invoked on them are marshalled and forwarded to remote implementations. Instances of M:I-servant are servants: these are normal Lisp classes that implement various methods. They can receive remote invocations. When a method is invoked on a servant no marshalling is done: the servant implementation code is invoked directly. Now suppose an interface named J extends I:
module M { //We are reopening the module M 
  interface K {;};
  interface J (I, K) {;};
  }
When compiled, the Lisp classes corresponding to J will have the following inheritance structure:
  1. M:J inherits from M:I and M:K
  2. M:J-servant inherits from M:I-servant and M:J-servant
  3. M:J-proxy inherits from M:I-proxy and M:K-proxy
Attributes are mapped to slots in the generated -servant class. For an attribute a:
  1. The slotname is op:a
  2. The reader name is op:a
  3. The writer name is (setf op:a) - unless the attribute is readonly, in which case no writer is generated.
  4. The initarg is :a
For example, M:J-servant inherits a slot named op:a1 from M:I-servant. An instance of M:J-servant can be created via
(setq j-serv (make-instance 'M:J-servant :a1 3958810))
The value of the attribute a1 of j-serv can be retrieved via:
(op:a1 j-serv)
----> 3958810
The value of the attribute can be changed via:
(setf (op:a1 j-serv) -3)
(op:a1 j-serv)
---> -3
The readers and writers can also be invoked on proxies. However, the initarg cannot be used for a proxy.

Mapping for operations

The mapping for operations is quite simple: if foo is an operation defined in some interface, then there is a corresponding Lisp method named op:foo. If foo is declared to return void and has no out arguments, then op:foo returns no values.

If foo is not declared to return void and has no out arguments, it returns a single value.

If foo is declared to return out arguments then they are returned as multiple values after whatever values foo would have returned without those out arguments.

The first argument to op:foo is the object on which it is invoked. The remaining arguments are the parameters of the operation. Consider the following IDL:

module M {
 interface optest {
   void testvoid (in long a);
   long testlong (in string b);
   long testmultiple (in string b, out char c);
   void testvoidmultiple (out char c1, out string s, out float f3);
};
};
Suppose that m is an object of type M:optest. Then an invocation sequence might look like:
(op:testvoid m -100000)
--->[No values returned]

(op:testlong m "hello")
--->599934

(op:testmultiple  m "goodbye")
---> 3000 #\G

(multiple-value-bind (a b c) ;Get the returned values
  (op:testvoidmultiple m)
 (list "The values returned are:" a b c))
---> ("The values returned are" #\Y "hurdle" 1.87)
Everything that "looks like a method" in Lisp is mapped to the op package, notably struct member accessors, union member accessors, attribute accessors, and so on.

structs

A struct follows the same naming rules as an interface. A struct named S defined in module M maps to a Lisp class named M:S. If S has a member named mem, then the Lisp class has a slot named op:mem with initarg :mem and accessor op:mem. The constructor for a struct has the same name as the struct. For example from the IDL:
module M {
  struct S {long foo; string fum;};};
You can create a new instance of the class M:S via
(setq struct (m:s :foo 300 :fum "test"))
---> #< M:S :FOO 300 :FUM "TEST">

(op:foo struct)
---> 300

(setf (op:fum struct) "passed")
---> "passed"

(op:fum struct)
---> "passed"

unions

A union follows the same naming rules as interfaces and structs. The member accessors and member initargs follow the usual pattern, except that of course only one initarg can be used in the initialization (since only one member can have a value). A union has two slots named op:union-value and op:union-discriminator. The name of the constructor for the union is the name of the union. Consider for example the following IDL:
module M { interface IU 
           { union V switch (long) {
               case 3: string foo;
               case 5: long fum;};};};
The name of the corresponding Lisp class is M:IU/V. (The "/" is a scoping separator). You can create a union with label 3 and value "echo" in the foo member via:
(setq u (M:IU/V :foo "echo"))
The value can be retrieved via:
(op:foo u)
----> "echo"

(op:union-value u)
---->"echo"

(op:union-discriminator u)
---->3

Exceptions

The mapping for exceptions is exactly like the mapping for structs, except that an IDL user-defined exception is a subclass of the Lisp condition class CORBA:userexception, which inherits from CORBA:exception, which inherits from condition.

Typedef and const definitions

You can define a named type or a named constant in IDL. As would be expected, these are mapped to Lisp types and constants respectively, following the usual naming rules:
module M {
 typedef string foo;
 const long r = 1 + 3;
  };
From the above IDL, we get:
(typep "hello" 'M:foo)
---->T
(typep 3 'M:foo)
---->nil
M:r
---->4

Understanding the rules for constructed types

The mapping rules were constructed with the primary goal of uniformity and ease of learning.

The fundamental abstraction used is that of a "type" with some kind of "named members". Insofar as possible, in each case such a type maps to a Lisp type of the same name, with a keyword initarg corresponding to each named member, and readers and writers for each named member. Each reader is in the op: package; the writer is formed via (setf reader). The constructor is simply the name of the type.