[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

FFI



>>From sysc.pdx.edu!marcus@tmpmbx.netmbx.de Wed Jun 12 11:12 MET 1996
>Return-Path: <sysc.pdx.edu!marcus@tmpmbx.netmbx.de>
>>Received: by tmpmbx.netmbx.de (/\==/\ Smail3.1.28.1 #28.6)
>	  id <m0uTlUo-00006iC>; Wed, 12 Jun 96 10:41 MES
>Date: Wed, 12 Jun 1996 00:54:02 -0700
>From: Marcus Daniels <marcus@sysc.pdx.edu>
>To: jack@andromeda.ubis.de
>Subject: FFI
>Reply-To: marcus@sysc.pdx.edu
>Content-Length: 22861
>
>
>                 The Foreign Function Call Facility
>                 ==================================
>
>A foreign function description is written as a Lisp file,
>and when compiled it produces a .c file which is then compiled
>by the C compiler and may be linked together with lisp.a.
>
>All symbols relating to the foreign function interface are exported from
>the package FFI. To use them, (USE-PACKAGE "FFI").
>
>Special FFI forms may appear anywhere in the Lisp file.
>
>                                Overview
>                                --------
>
>These are the special FFI forms. We have taken a pragmatic approach:
>the only foreign languages we support for now are C and ANSI C.
>
>(DEF-C-TYPE name <c-type>)
>
>(DEF-C-VAR name {option}*)
>  option ::=
>      (:name <c-name>)
>    | (:type <c-type>)
>    | (:read-only <boolean>)
>    | (:alloc <allocation>)
>
>(DEF-CALL-OUT name {option}*)
>  option ::=
>      (:name <c-name>)
>    | (:arguments {(arg-name <c-type> [<param-mode> [<allocation>]])}*)
>    | (:return-type <c-type> [<allocation>])
>    | (:language <language>)
>
>(DEF-CALL-IN name {option}*)
>  option ::=
>      (:name <c-name>)
>    | (:arguments {(arg-name <c-type> [<param-mode> [<allocation>]])}*)
>    | (:return-type <c-type> [<allocation>])
>    | (:language <language>)
>
>(DEF-C-CALL-OUT name {option}*)
>  option ::=
>      (:name <c-name>)
>    | (:arguments {(arg-name <c-type> [<param-mode> [<allocation>]])}*)
>    | (:return-type <c-type> [<allocation>])
>
>(DEF-C-CALL-IN name {option}*)
>  option ::=
>      (:name <c-name>)
>    | (:arguments {(arg-name <c-type> [<param-mode> [<allocation>]])}*)
>    | (:return-type <c-type> [<allocation>])
>
>(DEF-C-STRUCT name (<ident> <c-type>)*)
>
>(DEF-C-ENUM <name> {<ident> | (<ident> [<value>])}*)
>
>(ELEMENT c-place {index}*)
>(DEREF c-place)
>(SLOT c-place slot-name)
>(CAST c-place <c-type>)
>
>(TYPEOF c-place)
>(SIZEOF c-place), (SIZEOF <c-type>)
>(BITSIZEOF c-place), (BITSIZEOF <c-type>)
>
>(VALIDP foreign-entity)
>
>name is any Lisp symbol.
>
><c-name> is a string.
>
>                       (Foreign) C types
>                       -----------------
>
>Foreign C types are used in the FFI. They are *not* regular Common Lisp
>types or CLOS classes.
>
>A <c-type> is either a predefined C type or the name of a type defined by
>DEF-C-TYPE.
>
>The simple C types are these:
>
> Lisp name     Lisp equiv           C equiv        ILU equiv
>  nil           NIL                  void                             (o)
>  boolean       (MEMBER NIL T)       int            BOOLEAN
>  character     STRING-CHAR          char           SHORT CHARACTER
>  char          INTEGER              signed char
>  uchar         INTEGER              unsigned char
>  short         INTEGER              short
>  ushort        INTEGER              unsigned short
>  int           INTEGER              int
>  uint          INTEGER              unsigned int
>  long          INTEGER              long
>  ulong         INTEGER              unsigned long
>  uint8         (UNSIGNED-BYTE 8)    uint8          BYTE
>  sint8         (SIGNED-BYTE 8)      sint8
>  uint16        (UNSIGNED-BYTE 16)   uint16         SHORT CARDINAL
>  sint16        (SIGNED-BYTE 16)     sint16         SHORT INTEGER
>  uint32        (UNSIGNED-BYTE 32)   uint32         CARDINAL
>  sint32        (SIGNED-BYTE 32)     sint32         INTEGER
>  uint64        (UNSIGNED-BYTE 64)   uint64         LONG CARDINAL     (*)
>  sint64        (SIGNED-BYTE 64)     sint64         LONG INTEGER      (*)
>  single-float  SINGLE-FLOAT         float
>  double-float  DOUBLE-FLOAT         double
>(o) as a result type only.
>(*) does not work on all platforms.
>
>The predefined C types are:
>
>  c-type ::=
>      <simple-c-type>
>    | C-POINTER
>    | C-STRING
>    | (C-STRUCT <class> (<ident> <c-type>)*)
>    | (C-UNION (<ident> <c-type>)*)
>    | (C-ARRAY <c-type> dimensions)
>        dimensions ::= number | ({number}*)
>    | (C-ARRAY-MAX <c-type> maxdimension)
>        maxdimension ::= number
>    | (C-FUNCTION {option}*)
>        option ::=
>            (:arguments {(arg-name <c-type> [<param-mode> [<allocation>]])}*)
>          | (:return-type <c-type> [<allocation>])
>          | (:language <language>)
>    | (C-PTR <c-type>)
>    | (C-PTR-NULL <c-type>)
>    | (C-ARRAY-PTR <c-type>)
>
>(DEF-C-TYPE name <c-type>)
>makes name a shortcut for <c-type>. Note that <c-type> may already refer
>to name. Forward declarations of types are not possible, however.
>
>The type C-POINTER corresponds to what C calls "void*", an opaque pointer.
>
>The type C-STRING corresponds to what C calls "char*", a zero-terminated
>string. Its Lisp equivalent is a string, without the trailing zero character.
>
>The type (C-STRUCT class (ident1 type1) ... (ident2 type2)) is equivalent to
>what C calls "struct { type1 ident1; ...; type2 ident2; }". Its Lisp
>equivalent is: if class is VECTOR, a simple-vector; if class is LIST, a list;
>if class is a symbol naming a structure or CLOS class: an instance of this
>class, with slots of names ident1,...,ident2.
>
>The type (C-UNION (ident1 type1) ... (ident2 type2)) is equivalent to what C
>calls "union { type1 ident1; ...; type2 ident2; }". Conversion to and from
>Lisp assumes that a value is to be viewed as being of type1.
>
>The type (C-ARRAY type dim1 ... dim2) is equivalent to what C calls
>"type [dim1]...[dim2]". Note that when an array is passed as an argument to
>a function in C, it is actually passed as a pointer; you therefore have to
>write (C-PTR (C-ARRAY ...)) for this argument's type.
>
>The type (C-ARRAY-MAX type maxdim) is equivalent to what C calls
>"type [maxdim]", an array containing up to maxdim elements. The array is
>zero-terminated if it contains less than maxdim elements. Conversion from Lisp
>of an array with more than maxdim elements silently ignores the superfluous
>elements.
>
>The type (C-PTR type) is equivalent to what C calls "type *": a pointer to
>a single item of the given type.
>
>The type (C-PTR-NULL type) is also equivalent to what C calls 
>"type *": a pointer to a single item of the given type.  C-PTR-NULL
>implicits converts NIL into NULL.
>
>The type (C-ARRAY-PTR type) is equivalent to what C calls "type (*)[]":
>a pointer to a zero-terminated array of items of the given type.
>
>The type (C-FUNCTION (:return-type rtype) (:arguments (arg1 type1 ...) ...))
>designates a C function that can be called according to the given prototype
>(rtype (*) (type1, ...)).
>The <language> is either :C (denotes K&R C) or :STDC (denotes ANSI C). It
>specifies whether the C function has been compiled by a K&R C compiler or by
>an ANSI C compiler.
>Conversion between C functions and Lisp functions is transparent.
>
>(DEF-C-STRUCT <name> (<ident> <c-type>)*) defines <name> to be both a
>DEFSTRUCT structure type and a foreign C type with the given slots.
>
>(DEF-C-ENUM <name> {<ident> | (<ident> [<value>])}*) defines <ident>s as
>constants, similarly to the C declaration  enum { <ident> [= <value>], ... };
>
>The form (SIZEOF <c-type>) returns the size and alignment of a C type,
>measured in bytes.
>
>The form (BITSIZEOF <c-type>) returns the size and alignment of a C type,
>measured in bits.
>
>The predicate (VALIDP foreign-entity) returns NIL if the foreign-entity
>(e.g. the Lisp equivalent of a C-POINTER) refers to a pointer which is
>invalid because it comes from a previous Lisp session. It returns T if
>foreign-entity can be used within the current Lisp process.
>
>                       Foreign variables
>                       -----------------
>
>Foreign variables are variables whose storage is allocated in the foreign
>language module. They can nevertheless be evaluated and modified through SETQ,
>just as normal variables can, except that the range of allowed values is
>limited according to the variable's foreign type. Note that for a foreign
>variable X the form (EQL X X) is not necessarily true, since every time X is
>evaluated its foreign value is converted to a freshly created Lisp value.
>
>(DEF-C-VAR name {option}*)
>  option ::=
>      (:name <c-name>)
>    | (:type <c-type>)
>    | (:read-only <boolean>)
>    | (:alloc <allocation>)
>
>defines a foreign variable. `name' is the Lisp name, a regular Lisp symbol.
>
>The :name option specifies the name, as seen from C, as a string. If not
>specified, it is derived from the print name of the Lisp name.
>
>The :type option specifies the variable's foreign type.
>
>If the :read-only option is specified and non-NIL, it will be impossible
>to change the variable's value from within Lisp (using SETQ or similar).
>
>The :alloc option can be either :NONE or :MALLOC-FREE and defaults to
>:NONE.  If it is :MALLOC-FREE, any values of type C-STRING, 
>(C-PTR ...), (C-PTR-NULL ...), (C-ARRAY-PTR ...) within the foreign
>value are assumed to be pointers to malloc()-allocated storage, and
>when SETQ replaces an old value by a new one, the old storage is freed
>using free() and the new storage allocated using malloc(). If it is
>:NONE, SETQ assumes that the pointers point to good storage (not
>NULL!) and overwrites the old values by the new ones. This is
>dangerous (just think of overwriting a string with a longer one or
>storing some data in a NULL pointer...) and deprecated.
>
>                   Operations on foreign places
>                   ----------------------------
>
>A foreign variable `name' defined by DEF-C-VAR defines a "place", i.e.
>a form which can also be used as argument to SETF. (An "lvalue" in C
>terminology.) The following operations are available on foreign places:
>
>(ELEMENT place index1 ... indexn)
>Array element: If place is of foreign type (C-ARRAY <c-type> dim1 ... dimn)
>and 0 <= index1 < dim1, ..., 0 <= indexn < dimn, this will be the place
>corresponding to (aref place index1 ... indexn) or place[index1]...[indexn].
>It is a place of type <c-type>.
>If place is of foreign type (C-ARRAY-MAX <c-type> dim) and 0 <= index < dim,
>this will be the place corresponding to (aref place index) or place[index].
>It is a place of type <c-type>.
>
>(DEREF place) Dereference pointer: If place is of foreign type 
>(C-PTR <c-type>) or (C-PTR-NULL <c-type>) this will be the place the
>pointer points to. It is a place of type <c-type>.
>
>(SLOT place slot-name)
>Struct or union component: If place is of foreign type
>(C-STRUCT <class> ... (slot-name <c-type>) ...) or of type
>(C-UNION ... (slot-name <c-type>) ...), this will be of type <c-type>.
>
>(CAST place <c-type>)
>Type change: A place denoting the same memory locations as the original place,
>but of type <c-type>.
>
>(TYPEOF place)
>returns the <c-type> corresponding to the place.
>
>(SIZEOF place) returns the size and alignment of the C type of place,
>measured in bytes.
>
>(BITSIZEOF place) returns the size and alignment of the C type of place,
>measured in bits.
>
>                       Foreign functions
>                       -----------------
>
>Foreign functions are functions which are defined in the foreign language.
>There are named foreign functions (imported via DEF-CALL-OUT or created via
>DEF-CALL-IN) and anonymous foreign functions; they arise through conversion
>of function pointers.
>
>A "call-out" function is a foreign function called from Lisp: control flow
>temporarily leaves Lisp.
>A "call-in" function is a Lisp function called from the foreign language:
>control flow temporary enters Lisp.
>
>(DEF-CALL-OUT name {option}*)
>  option ::=
>      (:name <c-name>)
>    | (:arguments {(arg-name <c-type> [<param-mode> [<allocation>]])}*)
>    | (:return-type <c-type> [<allocation>])
>    | (:language <language>)
>
>defines a named call-out function. Any Lisp function call to #'name is
>redirected to call the C function <c-name>.
>
>DEF-C-CALL-OUT is equivalent to DEF-CALL-OUT with :LANGUAGE :C.
>
>(DEF-CALL-IN name {option}*)
>  option ::=
>      (:name <c-name>)
>    | (:arguments {(arg-name <c-type> [<param-mode> [<allocation>]])}*)
>    | (:return-type <c-type> [<allocation>])
>    | (:language <language>)
>
>defines a named call-in function. Any C function call to the C function
><c-name> is redirected to call the Lisp function #'name.
>
>DEF-C-CALL-IN is equivalent to DEF-CALL-IN with :LANGUAGE :C.
>
>              Argument and result passing conventions
>              ---------------------------------------
>
>When passed to and from functions, allocation of arguments and results is
>handled as follows:
>
>Values of <simple-c-type>, C-POINTER are passed on the stack, with dynamic
>extent. The <allocation> is effectively ignored.
>
>Values of type C-STRING, (C-PTR ...), (C-PTR-NULL ...), (C-ARRAY-PTR ...) 
>need storage. The <allocation> specifies the allocation policy:
>  <allocation> is :NONE          means that no storage is allocated.
>  <allocation> is :ALLOCA        means allocation of storage on the stack,
>                                       which has dynamic extent.
>  <allocation> is :MALLOC-FREE   means that storage will be allocated
>                                       via malloc() and freed via free().
>If no <allocation> is specified, the default <allocation> is :NONE for most
>types, but :ALLOCA for C-STRING, (C-PTR ...), (C-PTR-NULL ...), and
> (C-ARRAY-PTR ...) and for :OUT arguments. [Subject to change!]
>The :MALLOC-FREE policy provides the ability to pass arbitrarily nested
>structs containing pointers pointing to structs ... within a single conversion.
>
>For call-out functions:
>  For arguments passed from Lisp to C:
>    If <allocation> is :MALLOC-FREE,
>       Lisp allocates the storage using malloc() and never deallocates it.
>       The C function is supposed to call free() when done with it.
>    If <allocation> is :ALLOCA,
>       Lisp allocates the storage on the stack, with dynamic extent. It is
>       freed when the C function returns.
>    If <allocation> is :NONE,
>       Lisp assumes that the pointer already points to a valid area of the
>       proper size and puts the result value there. This is dangerous! and
>       deprecated.
>  For results passed from C to Lisp:
>    If <allocation> is :MALLOC-FREE,
>       Lisp calls free() on it when done.
>    If <allocation> is :NONE,
>       Lisp does nothing.
>For call-in functions:
>  For arguments passed from C to Lisp:
>    If <allocation> is :MALLOC-FREE,
>       Lisp calls free() on it when done.
>    If <allocation> is :ALLOCA or :NONE,
>       Lisp does nothing.
>  For results passed from Lisp to C:
>    If <allocation> is :MALLOC-FREE,
>       Lisp allocates the storage using malloc() and never deallocates it.
>       The C function is supposed to call free() when done with it.
>    If <allocation> is :NONE,
>       Lisp assumes that the pointer already points to a valid area of the
>       proper size and puts the result value there. This is dangerous! and
>       deprecated.
>
>A function parameter's <param-mode> may be
>either :IN (means: read-only):
>       The caller passes information to the callee.
>or     :OUT (means: write-only):
>       The callee passes information back to the caller on return.
>       When viewed as a Lisp function, there is no Lisp argument corresponding
>       to this, instead it means an additional return value.
>or     :IN-OUT (means: read-write):
>       Information is passed from the caller to the callee and then back to
>       the caller. When viewed as a Lisp function, the ":OUT" value is
>       returned as an additional multiple value.
>The default is :IN.
>
>[Currently, only :IN is fully implemented. :OUT works only with
><allocation> = :ALLOCA.]
>
>On AmigaOS, <allocation> may not be :MALLOC-FREE because there is no commonly
>used malloc()/free() library function.
>
>On AmigaOS, the <allocation> may be followed by a register specification,
>any of the symbols :D0, :D1, :D2, :D3, :D4, :D5, :D6, :D7, :A0, :A1, :A2,
>:A3, :A4, :A5, :A6, each representing one 680x0 register. This works only
>for integral types: integers, pointers, C-STRING, C-FUNCTION.
>
>Passing C-STRUCT, C-UNION, C-ARRAY, C-ARRAY-MAX values as arguments (not via
>pointers) is only possible to the extent the C compiler supports it. Most C
>compilers do it right, but some C compilers (such as gcc on hppa) have
>problems with this.
>
>                           Examples
>                           --------
>
>Ex. 1: The C declaration
>
>       struct foo {
>           int a;
>           struct foo * b[100];
>       };
>
>corresponds to
>
>       (def-c-struct foo
>         (a int)
>         (b (c-array (c-ptr foo) 100))
>       )
>
>The element access
>
>       struct foo f;
>       f.b[7].a
>
>corresponds to
>
>       (declare (type foo f))
>       (foo-a (aref (foo-b f) 7)) or (slot-value (aref (slot-value f 'b) 7) 'a)
>
>Ex. 2: Here is an example of an external C variable and some accesses:
>
>       struct bar {
>           short x, y;
>           char a, b;
>           int z;
>           struct bar * n;
>       };
>
>       extern struct bar * my_struct;
>
>       my_struct->x++;
>       my_struct->a = 5;
>       my_struct = my_struct->n;
>
>corresponds to
>
>       (def-c-struct bar
>         (x short)
>         (y short)
>         (a char)
>         (b char) ; or (b character) if it represents a character, not a number
>         (z int)
>         (n (c-ptr bar))
>       )
>
>       (def-c-var my_struct (:type (c-ptr bar)))
>
>       (setq my_struct (let ((s my_struct)) (incf (slot-value s 'x)) s))
>       or (incf (slot my_struct 'x))
>       (setq my_struct (let ((s my_struct)) (setf (slot-value s 'a) 5) s))
>       or (setf (slot my_struct 'a) 5)
>       (setq my_struct (slot-value my_struct 'n))
>       or (setq my_struct (deref (slot my_struct 'n)))
>
>Ex. 3: An example for calling an external function:
>On ANSI C systems, <stdlib.h> contains the declarations
>
>       typedef struct {
>         int quot;   /* Quotient */
>         int rem;    /* Remainder */
>       } div_t;
>       extern div_t div (int numer, int denom);
>
>This translates to
>
>       (def-c-struct div_t
>         (quot int)
>         (rem int)
>       )
>       (def-c-call-out div (:arguments (numer int) (denom int))
>                           (:return-type div_t)
>       )
>
>Sample call from within Lisp:
>
>       > (div 20 3)
>       #S(DIV :QUOT 6 :REM 2)
>
>Ex. 4: Another example for calling an external function:
>
>Suppose the following is defined in a file "cfun.c":
>
>       struct cfunr { int x; char *s; };
>       struct cfunr * cfun (i,s,r,a)
>           int i;
>           char *s;
>           struct cfunr * r;
>           int a[10];
>       {
>           int j;
>           struct cfunr * r2;
>           printf("i = %d\n", i);
>           printf("s = %s\n", s);
>           printf("r->x = %d\n", r->x);
>           printf("r->s = %s\n", r->s);
>           for (j = 0; j < 10; j++) printf("a[%d] = %d.\n", j, a[j]);
>           r2 = (struct cfunr *) malloc (sizeof (struct cfunr));
>           r2->x = i+5;
>           r2->s = "A C string";
>           return r2;
>       }
>
>It is possible to call this function from Lisp using the file "callcfun.lsp"
>(don't call it "cfun.lsp" - COMPILE-FILE would overwrite "cfun.c") whose
>contents is:
>
>       (in-package "TEST-C-CALL" :use '("LISP" "FFI"))
>       (def-c-struct cfunr (x int) (s c-string))
>       (def-c-call-out cfun (:arguments (i int)
>                                        (s c-string)
>                                        (r (c-ptr cfunr) :in :alloca)
>                                        (a (c-ptr (c-array int 10)) :in
:alloca)
>                            )
>                            (:return-type (c-ptr cfunr))
>       )
>       (defun call-cfun ()
>         (cfun 5 "A Lisp string" (make-cfunr :x 10 :s "Another Lisp string")
>               '#(0 1 2 3 4 5 6 7 8 9)
>       ) )
>
>Use the module facility:
>
>       $ clisp-link create-module-set cfun callcfun.c
>       $ cc -O -c cfun.c
>       $ cd cfun
>       $ ln -s ../cfun.o cfun.o
>       Add cfun.o to NEW_LIBS and NEW_FILES in link.sh.
>       $ cd ..
>       $ base/lisp.run -M base/lispinit.mem -c callcfun.lsp
>       $ clisp-link add-module-set cfun base base+cfun
>       $ base+cfun/lisp.run -M base+cfun/lispinit.mem -i callcfun
>       > (test-c-call::call-cfun)
>       i = 5
>       s = A Lisp string
>       r->x = 10
>       r->s = Another Lisp string
>       a[0] = 0.
>       a[1] = 1.
>       a[2] = 2.
>       a[3] = 3.
>       a[4] = 4.
>       a[5] = 5.
>       a[6] = 6.
>       a[7] = 7.
>       a[8] = 8.
>       a[9] = 9.
>       #S(TEST-C-CALL::CFUNR :X 10 :S "A C string")
>       > 
>       $ rm -r base+cfun
>
>Note that there is a memory leak here: The return value r2 of cfun() is
>malloc()ed but never free()d. Specifying
>       (:return-type (c-ptr cfunr) :malloc-free)
>is not an alternative because this would also free(r2->x) but r2->x is a
>pointer to static data.
>
>Ex. 5: To sort an array of double-floats using the Lisp function SORT
>instead of the C library function qsort(), one can use the following
>interface code "sort1.c". The main problem is to pass a variable-sized array.
>
>       extern void lispsort_begin (int);
>       void* lispsort_function;
>       void lispsort_double (int n, double * array)
>       {
>           double * sorted_array;
>           int i;
>           lispsort_begin(n); /* store #'sort2 in lispsort_function */
>           sorted_array = ((double * (*) (double *)) lispsort_function)
(array);
>           for (i = 0; i < n; i++) array[i] = sorted_array[i];
>           free(sorted_array);
>       }
>
>This is accompanied by "sort2.lsp":
>
>       (use-package "FFI")
>       (def-call-in lispsort_begin (:arguments (n int))
>                                   (:return-type nil)
>                                   (:language :stdc)
>       )
>       (def-c-var lispsort_function (:type c-pointer))
>       (defun lispsort_begin (n)
>         (setf (cast lispsort_function
>                     `(c-function
>                        (:arguments (v (c-ptr (c-array double-float ,n))))
>                        (:return-type (c-ptr (c-array double-float ,n))
>                                      :malloc-free
>                      ) )
>               )
>               #'sort2
>       ) )
>       (defun sort2 (v)
>         (declare (type vector v))
>         (sort v #'<)
>       )
>
>To test this, use the following test file "sorttest.lsp":
>
>       (def-call-out sort10
>                     (:name "lispsort_double")
>                     (:language :stdc)
>                     (:arguments (n int)
>                                 (array (c-ptr (c-array double-float 10))
>                                        :in-out
>       )             )           )
>
>Now try
>
>       $ clisp-link create-module-set sort sort2.c sorttest.c
>       $ cc -O -c sort1.c
>       $ cd sort
>       $ ln -s ../sort1.o sort1.o
>       Add sort1.o to NEW_LIBS and NEW_FILES in link.sh.
>       $ cd ..
>       $ base/lisp.run -M base/lispinit.mem -c sort2.lsp sorttest.lsp
>       $ clisp-link add-module-set sort base base+sort
>       $ base+sort/lisp.run -M base+sort/lispinit.mem -i sort2 sorttest
>       > (sort10 10 '#(0.501d0 0.528d0 0.615d0 0.550d0 0.711d0
>                       0.523d0 0.585d0 0.670d0 0.271d0 0.063d0))
>       #(0.063d0 0.271d0 0.501d0 0.523d0 0.528d0 0.55d0 0.585d0 0.615d0
0.67d0 0.711d0)
>       $ rm -r base+sort
>
>
>
>
-----------------------------------------------------------------
Unternehmensberatung fuer integrierte Systeme
UBIS GmbH
Jacques Henke
Alt-Moabit 98
D-10559 Berlin
tel: +49 30 399 29 - 753
fax: +49 30 399 29 - 900

e-mail: jack@ubis.de
www: http://www.ubis.de
-----------------------------------------------------------------