topheader Welcome 44.222.196.236 @ xps.scios.ch on Fri Mar 29 4:48:04 UTC 2024
 
topheader
 

8 SBCL Foreign Function Interface

This chapter describes SBCL's interface to C programs and libraries (and, since C interfaces are a sort of lingua franca of the Unix world, to other programs and libraries in general.)

Note: In the modern Lisp world, the usual term for this functionality is Foreign Function Interface, or FFI, where despite the mention of “function” in this term, FFI also refers to direct manipulation of C data structures as well as functions. The traditional CMUCL terminology is Alien Interface, and while that older terminology is no longer used much in the system documentation, it still reflected in names in the implementation, notablyin the name of the SB-ALIEN package.

8.1 Introduction to the Foreign Function Interface

Because of Lisp's emphasis on dynamic memory allocation and garbage collection, Lisp implementations use non-C-like memory representations for objects. This representation mismatch creates friction when a Lisp program must share objects with programs which expect C data. There are three common approaches to establishing communication:

  • The burden can be placed on the foreign program (and programmer) by requiring the knowledge and use of the representations used internally by the Lisp implementation. This can require a considerable amount of “glue” code on the C side, and that code tends to be sensitively dependent on the internal implementation details of the Lisp system.
  • The Lisp system can automatically convert objects back and forth between the Lisp and foreign representations. This is convenient, but translation becomes prohibitively slow when large or complex data structures must be shared. This approach is supported by the SBCL FFI, and used automatically by the when passing integers and strings.
  • The Lisp program can directly manipulate foreign objects through the use of extensions to the Lisp language.

SBCL, like CMUCL before it, relies primarily on the automatic conversion and direct manipulation approaches. The SB-ALIEN package provides a facility wherein foreign values of simple scalar types are automatically converted and complex types are directly manipulated in their foreign representation. Additionally the lower-level System Area Pointers (or SAPs) can be used where necessary to provide untyped access to foreign memory.

Any foreign objects that can't automatically be converted into Lisp values are represented by objects of type alien-value. Since Lisp is a dynamically typed language, even foreign objects must have a run-time type; this type information is provided by encapsulating the raw pointer to the foreign data within an alien-value object.

The type language and operations on foreign types are intentionally similar to those of the C language.

8.2 Foreign Types

Alien types have a description language based on nested list structure. For example the C type

     struct foo {
         int a;
         struct foo *b[100];
     };

has the corresponding SBCL FFI type

     (struct foo
       (a int)
       (b (array (* (struct foo)) 100)))

8.2.1 Defining Foreign Types

Types may be either named or anonymous. With structure and union types, the name is part of the type specifier, allowing recursively defined types such as:

                    (struct foo (a (* (struct foo)))) 

An anonymous structure or union type is specified by using the name nil. The with-alien macro defines a local scope which “captures” any named type definitions. Other types are not inherently named, but can be given named abbreviations using the define-alien-type macro.

8.2.2 Foreign Types and Lisp Types

The foreign types form a subsystem of the SBCL type system. An alien type specifier provides a way to use any foreign type as a Lisp type specifier. For example,

     (typep foo '(alien (* int)))

can be used to determine whether foo is a pointer to a foreign int. alien type specifiers can be used in the same ways as ordinary Lisp type specifiers (like string.) Alien type declarations are subject to the same precise type checking as any other declaration. See Precise Type Checking.

Note that the type identifiers used in the foreign type system overlap with native Lisp type specifiers in some cases. For example, the type specifier (alien single-float) is identical to single-float, since foreign floats are automatically converted to Lisp floats. When type-of is called on an alien value that is not automatically converted to a Lisp value, then it will return an alien type specifier.

8.2.3 Foreign Type Specifiers

Note: All foreign type names are exported from the sb-alien package. Some foreign type names are also symbols in the common-lisp package, in which case they are reexported from the sb-alien package, so that e.g. it is legal to refer to sb-alien:single-float.

These are the basic foreign type specifiers:

The foreign type specifier (* foo) describes a pointer to an object of type foo. A pointed-to type foo of t indicates a pointer to anything, similar to void * in ANSI C. A null alien pointer can be detected with the sb-alien:null-alien function.

The foreign type specifier (array foo &rest dimensions) describes array of the specified dimensions, holding elements of type foo. Note that (unlike in C) (\* foo) and (array foo) are considered to be different types when type checking is done. If equivalence of pointer and array types is desired, it may be explicitly coerced using sb-alien:cast.

Arrays are accessed using sb-alien:deref, passing the indices as additional arguments. Elements are stored in column-major order (as in C), so the first dimension determines only the size of the memory block, and not the layout of the higher dimensions. An array whose first dimension is variable may be specified by using nil as the first dimension. Fixed-size arrays can be allocated as array elements, structure slots or sb-alien:with-alien variables. Dynamic arrays can only be allocated using sb-alien:make-alien.

The foreign type specifier (sb-alien:struct name &rest fields) describes a structure type with the specified name and fields. Fields are allocated at the same offsets used by the implementation's C compiler, as guessed by the SBCL internals. An optional :alignment keyword argument can be specified for each field to explicitly control the alignment of a field. If name is nil then the structure is anonymous.

If a named foreign struct specifier is passed to define-alien-type or with-alien, then this defines, respectively, a new global or local foreign structure type. If no fields are specified, then the fields are taken from the current (local or global) alien structure type definition of name.

The foreign type specifier (sb-alien:union name &rest fields) is similar to sb-alien:struct, but describes a union type. All fields are allocated at the same offset, and the size of the union is the size of the largest field. The programmer must determine which field is active from context.

The foreign type specifier (sb-alien:enum name &rest specs) describes an enumeration type that maps between integer values and symbols. If name is nil, then the type is anonymous. Each element of the specs list is either a Lisp symbol, or a list (symbol value). value is an integer. If value is not supplied, then it defaults to one greater than the value for the preceding spec (or to zero if it is the first spec).

The foreign type specifier (sb-alien:signed &optional bits) specifies a signed integer with the specified number of bits precision. The upper limit on integer precision is determined by the machine's word size. If bits is not specified, the maximum size will be used.

The foreign type specifier (integer &optional bits) is equivalent to the corresponding type specifier using sb-alien:signed instead of integer.

The foreign type specifier (sb-alien:unsigned &optional bits) is like corresponding type specifier using sb-alien:signed except that the variable is treated as an unsigned integer.

The foreign type specifier (boolean &optional bits) is similar to an enumeration type, but maps from Lisp nil and t to C 0 and 1 respectively. bits determines the amount of storage allocated to hold the truth value.

The foreign type specifier single-float describes a floating-point number in IEEE single-precision format.

The foreign type specifier double-float describes a floating-point number in IEEE double-precision format.

The foreign type specifier (function result-type &rest arg-types) describes a foreign function that takes arguments of the specified arg-types and returns a result of type result-type. Note that the only context where a foreign function type is directly specified is in the argument to sb-alien:alien-funcall. In all other contexts, foreign functions are represented by foreign function pointer types: (* (function …)).

The foreign type specifier sb-alien:system-area-pointer describes a pointer which is represented in Lisp as a system-area-pointer object. SBCL exports this type from sb-alien because CMUCL did, but tentatively (as of the first draft of this section of the manual, SBCL 0.7.6) it is deprecated, since it doesn't seem to be required by user code.

The foreign type specifier sb-alien:void is used in function types to declare that no useful value is returned. Using alien-funcall to call a void foreign function will return zero values.

The foreign type specifier (sb-alien:c-string &key external-format element-type) is similar to (* char), but is interpreted as a null-terminated string, and is automatically converted into a Lisp string when accessed; or if the pointer is C NULL or 0, then accessing it gives Lisp nil.

External format conversion is automatically done when Lisp strings are passed to foreign code, or when foreign strings are passed to Lisp code. If the type specifier has an explicit external-format, that external format will be used. Otherwise a default external format that has been determined at SBCL startup time based on the current locale settings will be used. For example, when the following alien routine is called, the Lisp string given as argument is converted to an ebcdic octet representation.

 (define-alien-routine test int (str (c-string :external-forma:ebcdic-us)))

Lisp strings of type base-string are stored with a trailing NUL termination, so no copying (either by the user or the implementation) is necessary when passing them to foreign code, assuming that the external-format and element-type of the c-string type are compatible with the internal representation of the string. For an SBCL built with Unicode support that means an external-format of :ascii and an element-type of base-char. Without Unicode support the external-format can also be :iso-8859-1, and the element-type can also be character. If the external-format or element-type is not compatible, or the string is a (simple-array character (*)), this data is copied by the implementation as required.

Assigning a Lisp string to a c-string structure field or variable stores the contents of the string to the memory already pointed to by that variable. When a foreign object of type (* char) is assigned to a c-string, then the c-string pointer is assigned to. This allows c-string pointers to be initialized. For example:

   (cl:in-package "CL-USER") ; which USEs package "SB-ALIEN"
 
   (define-alien-type nil (struct foo (str c-string)))
 
   (defun make-foo (str)
      (let ((my-foo (make-alien (struct foo))))
        (setf (slot my-foo 'str) (make-alien char (length str))
        (slot my-foo 'str) str)
         my-foo))

Storing Lisp NIL in a c-string writes C NULL to the variable.

sb-alien also exports translations of these C type specifiers as foreign type specifiers: sb-alien:char, sb-alien:short, sb-alien:int, sb-alien:long, sb-alien:unsigned-char, sb-alien:unsigned-short, sb-alien:unsigned-int, sb-alien:unsigned-long, sb-alien:float, and sb-alien:double.

8.3 Operations On Foreign Values

This section describes how to read foreign values as Lisp values, how to coerce foreign values to different kinds of foreign values, and how to dynamically allocate and free foreign variables.

8.3.1 Accessing Foreign Values

Function: sb-alien:deref pointer-or-array &rest indices

The sb-alien:deref function returns the value pointed to by a foreign pointer, or the value of a foreign array element. When dereferencing a pointer, an optional single index can be specified to give the equivalent of C pointer arithmetic; this index is scaled by the size of the type pointed to. When dereferencing an array, the number of indices must be the same as the number of dimensions in the array type. deref can be set with setf to assign a new value.

Function: sb-alien:slot struct-or-union slot-name

The sb-alien:slot function extracts the value of the slot named slot-name from a foreign struct or union. If struct-or-union is a pointer to a structure or union, then it is automatically dereferenced. sb-alien:slot can be set with setf to assign a new value. Note that slot-name is evaluated, and need not be a compile-time constant (but only constant slot accesses are efficiently compiled).

8.3.1.1 Untyped memory

As noted at the beginning of the chapter, the System Area Pointer facilities allow untyped access to foreign memory. SAPs can be converted to and from the usual typed foreign values using sap-alien and alien-sap (described elsewhere), and also to and from integers - raw machine addresses. They should thus be used with caution; corrupting the Lisp heap or other memory with SAPs is trivial.

Function: sb-sys:int-sap machine-address

Creates a SAP pointing at the virtual address machine-address.

Function: sb-sys:sap-ref-32 sap offset

Access the value of the memory location at offset bytes from sap. This form may also be used with setf to alter the memory at that location.

Function: sb-sys:sap= sap1 sap2

Compare sap1 and sap2 for equality.

Similarly named functions exist for accessing other sizes of word, other comparisons, and other conversions. The reader is invited to use apropos and describe for more details

     (apropos "sap" :sb-sys)

8.3.2 Coercing Foreign Values

Function: sb-alien:addr alien-expr

The sb-alien:addr macro returns a pointer to the location specified by alien-expr, which must be either a foreign variable, a use of sb-alien:deref, a use of sb-alien:slot, or a use of sb-alien:extern-alien.

Function: sb-alien:cast foreign-value new-type

The sb-alien:cast macro converts foreign-value to a new foreign value with the specified new-type. Both types, old and new, must be foreign pointer, array or function types. Note that the resulting Lisp foreign variable object is not eq to the argument, but it does refer to the same foreign data bits.

Function: sb-alien:sap-alien sap type

The sb-alien:sap-alien function converts sap (a system area pointer) to a foreign value with the specified type. type is not evaluated.

The type must be some foreign pointer, array, or record type.

Function: sb-alien:alien-sap foreign-value

The sb-alien:alien-sap function returns the SAP which points to alien-value's data. The foreign-value must be of some foreign pointer, array, or record type.

8.3.3 Foreign Dynamic Allocation

Lisp code can call the C standard library functions malloc and free to dynamically allocate and deallocate foreign variables. The Lisp code shares the same allocator with foreign C code, so it's OK for foreign code to call free on the result of Lisp sb-alien:make-alien, or for Lisp code to call sb-alien:free-alien on foreign objects allocated by C code.

Macro: sb-alien:make-alien type size

The sb-alien:make-alien macro returns a dynamically allocated foreign value of the specified type (which is not evaluated.) The allocated memory is not initialized, and may contain arbitrary junk. If supplied, size is an expression to evaluate to compute the size of the allocated object. There are two major cases:

When type is a foreign array type, an array of that type is allocated and a pointer to it is returned. Note that you must use deref to change the result to an array before you can use deref to read or write elements:

      (cl:in-package "CL-USER") ; which USEs package "SB-ALIEN"
      (defvar *foo* (make-alien (array char 10)))
      (type-of *foo*) => (alien (* (array (signed 8) 10)))
      (setf (deref (deref foo) 0) 10) => 10

If supplied, size is used as the first dimension for the array.

When type is any other foreign type, then an object for that type is allocated, and a pointer to it is returned. So (make-alien int) returns a (* int). If size is specified, then a block of that many objects is allocated, with the result pointing to the first one.

Function: sb-alien:free-alien foreign-value

The sb-alien:free-alien function frees the storage for foreign-value, which must have been allocated with Lisp make-alien or C malloc. See also the sb-alien:with-alien macro, which allocates foreign values on the stack.

8.4 Foreign Variables

Both local (stack allocated) and external (C global) foreign variables are supported.

8.4.1 Local Foreign Variables

Macro: sb-alien:with-alien var-definitions &body body

The with-alien macro establishes local foreign variables with the specified alien types and names. This form is analogous to defining a local variable in C: additional storage is allocated, and the initial value is copied. This form is less analogous to LET-allocated Lisp variables, since the variables can't be captured in closures: they live only for the dynamic extent of the body, and referring to them outside is a gruesome error.

The var-definitions argument is a list of variable definitions, each of the form

              (name type &optional initial-value)

The names of the variables are established as symbol-macros; the bindings have lexical scope, and may be assigned with setq or setf.

The with-alien macro also establishes a new scope for named structures and unions. Any type specified for a variable may contain named structure or union types with the slots specified. Within the lexical scope of the binding specifiers and body, a locally defined foreign structure type foo can be referenced by its name using (struct foo).

8.4.2 External Foreign Variables

External foreign names are strings, and Lisp names are symbols. When an external foreign value is represented using a Lisp variable, there must be a way to convert from one name syntax into the other. The macros extern-alien, define-alien-variable and define-alien-routine use this conversion heuristic:

Alien names are converted to Lisp names by uppercasing and replacing underscores with hyphens.

Conversely, Lisp names are converted to alien names by lowercasing and replacing hyphens with underscores.

Both the Lisp symbol and alien string names may be separately specified by using a list of the form

                (alien-string lisp-symbol)

Macro: sb-alien:define-alien-variable name type

The define-alien-variable macro defines name as an external foreign variable of the specified foreign type. name and type are not evaluated. The Lisp name of the variable (see above) becomes a global alien variable. Global alien variables are effectively “global symbol macros”; a reference to the variable fetches the contents of the external variable. Similarly, setting the variable stores new contents – the new contents must be of the declared type. Someday, they may well be implemented using the ANSI define-symbol-macro mechanism, but as of SBCL 0.7.5, they are still implemented using an older more-or-less parallel mechanism inherited from CMUCL.

For example, to access a C-level counter foo, one could write

    (define-alien-variable "foo" int)
    ;; Now it is possible to get the value of the C variable foo simply by
    ;; referencing that Lisp variable:
    (print foo)
    (setf foo 14)
    (incf foo)

Function: sb-alien:get-errno

Since in modern C libraries, the errno “variable” is typically no longer a variable, but some bizarre artificial construct which behaves superficially like a variable within a given thread, it can no longer reliably be accessed through the ordinary define-alien-variable mechanism. Instead, SBCL provides the operator sb-alien:get-errno to allow Lisp code to read it.

Macro: sb-alien:extern-alien name type

The extern-alien macro returns an alien with the specified type which points to an externally defined value. name is not evaluated, and may be either a string or a symbol. type is an unevaluated alien type specifier.

8.5 Foreign Data Structure Examples

Now that we have alien types, operations and variables, we can manipulate foreign data structures. This C declaration

     struct foo {
         int a;
         struct foo *b[100];
     };

can be translated into the following alien type:

     (define-alien-type nil
       (struct foo
         (a int)
         (b (array (* (struct foo)) 100))))

Once the foo alien type has been defined as above, the C expression

     struct foo f;
     f.b[7].a;

can be translated in this way:

     (with-alien ((f (struct foo)))
       (slot (deref (slot f 'b) 7) 'a)
       ;;
       ;; Do something with f...
       )

Or consider this example of an external C variable and some accesses:

     struct c_struct {
             short x, y;
             char a, b;
             int z;
             c_struct *n;
     };
     extern struct c_struct *my_struct;
     my_struct->x++;
     my_struct->a = 5;
     my_struct = my_struct->n;

which can be manipulated in Lisp like this:

     (define-alien-type nil
       (struct c-struct
               (x short)
               (y short)
               (a char)
               (b char)
               (z int)
               (n (* c-struct))))
     (define-alien-variable "my_struct" (* c-struct))
     (incf (slot my-struct 'x))
     (setf (slot my-struct 'a) 5)
     (setq my-struct (slot my-struct 'n))

8.6 Loading Shared Object Files

Foreign object files can be loaded into the running Lisp process by calling load-shared-object.

Function: sb-alien:load-shared-object pathname &key dont-save

Load a shared library / dynamic shared object file / similar foreign container specified by designated pathname, such as a .so on an elf platform.

Locating the shared object follows standard rules of the platform, consult the manual page for dlopen(3) for details. Typically paths speficied by environment variables such as LD_LIBRARY_PATH are searched if the pathname has no directory, but on some systems (eg. Mac os X) search may happen even if pathname is absolute. (On Windows LoadLibrary is used instead of dlopen(3).)

On non-Windows platoforms calling load-shared-object again with an pathname equal to the designated pathname of a previous call will replace the old definitions; if a symbol was previously referenced thru the object and is not present in the reloaded version an error will be signalled. Reloading may not work as expected if user or library-code has called dlopen(3) on the same shared object.

load-shared-object interacts with sb-ext:save-lisp-and-die:

1. If dont-save is true (default is NIL), the shared object will be dropped when save-lisp-and-die is called – otherwise shared objects are reloaded automatically when a saved core starts up. Specifying dont-save can be useful when the location of the shared object on startup is uncertain.

2. On most platforms references in compiled code to foreign symbols in shared objects (such as those generated by DEFINE-ALIEN-ROUTINE) remain valid across save-lisp-and-die. On those platforms where this is not supported, a warning will be signalled when the core is saved – this is orthogonal from dont-save.

8.7 Foreign Function Calls

The foreign function call interface allows a Lisp program to call many functions written in languages that use the C calling convention.

Lisp sets up various signal handling routines and other environment information when it first starts up, and expects these to be in place at all times. The C functions called by Lisp should not change the environment, especially the signal handlers: the signal handlers installed by Lisp typically have interesting flags set (e.g to request machine context information, or for signal delivery on an alternate stack) which the Lisp runtime relies on for correct operation. Precise details of how this works may change without notice between versions; the source, or the brain of a friendly SBCL developer, is the only documentation. Users of a Lisp built with the :sb-thread feature should also read the section about threads, Threading.

8.7.1 The alien-funcall Primitive

Function: sb-alien:alien-funcall alien-function &rest arguments

The alien-funcall function is the foreign function call primitive: alien-function is called with the supplied arguments and its C return value is returned as a Lisp value. The alien-function is an arbitrary run-time expression; to refer to a constant function, use extern-alien or a value defined by define-alien-routine.

The type of alien-function must be (alien (function …)) or (alien (* (function …))). The function type is used to determine how to call the function (as though it was declared with a prototype.) The type need not be known at compile time, but only known-type calls are efficiently compiled. Limitations:

  • Structure type return values are not implemented.
  • Passing of structures by value is not implemented.

Here is an example which allocates a (struct foo), calls a foreign function to initialize it, then returns a Lisp vector of all the (* (struct foo)) objects filled in by the foreign call:

     ;; Allocate a foo on the stack.
     (with-alien ((f (struct foo)))
       ;; Call some C function to fill in foo fields.
       (alien-funcall (extern-alien "mangle_foo" (function void (* foo)))
                      (addr f))
       ;; Find how many foos to use by getting the A field.
       (let* ((num (slot f 'a))
              (result (make-array num)))
       ;; Get a pointer to the array so that we don't have to keep
          extracting it:
         (with-alien ((a (* (array (* (struct foo)) 100)) (addr (slot f 'b))))
           ;; Loop over the first N elements and stash them in the result vector.
           (dotimes (i num)
             (setf (svref result i) (deref (deref a) i)))
           ;; Voila.
           result)))

8.7.2 The define-alien-routine Macro

Macro: sb-alien:define-alien-routine name result-type &rest arg-specifiers

The define-alien-routine macro is a convenience for automatically generating Lisp interfaces to simple foreign functions. The primary feature is the parameter style specification, which translates the C pass-by-reference idiom into additional return values.

name is usually a string external symbol, but may also be a symbol Lisp name or a list of the foreign name and the Lisp name. If only one name is specified, the other is automatically derived as for extern-alien. result-type is the alien type of the return value.

Each element of the arg-specifiers list specifies an argument to the foreign function, and is of the form

              (aname atype &optional style)

aname is the symbol name of the argument to the constructed function (for documentation). atype is the alien type of corresponding foreign argument. The semantics of the actual call are the same as for alien-funcall. style specifies how this argument should be handled at call and return time, and should be one of the following:

  • :in specifies that the argument is passed by value. This is the default. :in arguments have no corresponding return value from the Lisp function.
  • :copy is similar to :in, but the argument is copied to a pre-allocated object and a pointer to this object is passed to the foreign routine.
  • :out specifies a pass-by-reference output value. The type of the argument must be a pointer to a fixed-sized object (such as an integer or pointer). :out and :in-out style cannot be used with pointers to arrays, records or functions. An object of the correct size is allocated on the stack, and its address is passed to the foreign function. When the function returns, the contents of this location are returned as one of the values of the Lisp function (and the location is automatically deallocated).
  • :in-out is a combination of :copy and :out. The argument is copied to a pre-allocated object and a pointer to this object is passed to the foreign routine. On return, the contents of this location is returned as an additional value.

Note: Any efficiency-critical foreign interface function should be inline expanded, which can be done by preceding the define-alien-routine call with:

                       (declaim (inline lisp-name))

In addition to avoiding the Lisp call overhead, this allows pointers, word-integers and floats to be passed using non-descriptor representations, avoiding consing.)

8.7.3 define-alien-routine Example

Consider the C function cfoo with the following calling convention:

     void
     cfoo (str, a, i)
         char *str;
         char *a; /* update */
         int *i; /* out */
     {
       /* body of cfoo(...) */
     }

This can be described by the following call to define-alien-routine:

     (define-alien-routine "cfoo" void
       (str c-string)
       (a char :in-out)
       (i int :out))

The Lisp function cfoo will have two arguments (str and a) and two return values (a and i).

8.7.4 Calling Lisp From C

Calling Lisp functions from C is sometimes possible, but is extremely hackish and poorly supported as of SBCL 0.7.5. See funcall0 … funcall3 in the runtime system. The arguments must be valid SBCL object descriptors (so that e.g. fixnums must be left-shifted by 2.) As of SBCL 0.7.5, the format of object descriptors is documented only by the source code and, in parts, by the old CMUCL INTERNALS documentation.

Note that the garbage collector moves objects, and won't be able to fix up any references in C variables. There are three mechanisms for coping with this:

  1. The sb-ext:purify moves all live Lisp data into static or read-only areas such that it will never be moved (or freed) again in the life of the Lisp session
  2. sb-sys:with-pinned-objects is a macro which arranges for some set of objects to be pinned in memory for the dynamic extent of its body forms. On ports which use the generational garbage collector (as of SBCL 0.8.3, only the x86) this has a page granularity - i.e. the entire 4k page or pages containing the objects will be locked down. On other ports it is implemented by turning off GC for the duration (so could be said to have a whole-world granularity).
  3. Disable GC, using the without-gcing macro.

8.8 Step-By-Step Example of the Foreign Function Interface

This section presents a complete example of an interface to a somewhat complicated C function.

Suppose you have the following C function which you want to be able to call from Lisp in the file test.c

     struct c_struct
     {
       int x;
       char *s;
     };
 
     struct c_struct *c_function (i, s, r, a)
         int i;
         char *s;
         struct c_struct *r;
         int a[10];
     {
       int j;
       struct c_struct *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 c_struct *) malloc (sizeof(struct c_struct));
       r2->x = i + 5;
       r2->s = "a C string";
       return(r2);
     };

It is possible to call this C function from Lisp using the file test.lisp containing

     (cl:defpackage "TEST-C-CALL" (:use "CL" "SB-ALIEN" "SB-C-CALL"))
     (cl:in-package "TEST-C-CALL")
 
     ;;; Define the record C-STRUCT in Lisp.
     (define-alien-type nil
         (struct c-struct
                 (x int)
                 (s c-string)))
 
     ;;; Define the Lisp function interface to the C routine.  It returns a
     ;;; pointer to a record of type C-STRUCT.  It accepts four parameters:
     ;;; I, an int; S, a pointer to a string; R, a pointer to a C-STRUCT
     ;;; record; and A, a pointer to the array of 10 ints.
     ;;;
     ;;; The INLINE declaration eliminates some efficiency notes about heap
     ;;; allocation of alien values.
     (declaim (inline c-function))
     (define-alien-routine c-function
         (* (struct c-struct))
       (i int)
       (s c-string)
       (r (* (struct c-struct)))
       (a (array int 10)))
 
     ;;; a function which sets up the parameters to the C function and
     ;;; actually calls it
     (defun call-cfun ()
       (with-alien ((ar (array int 10))
                    (c-struct (struct c-struct)))
         (dotimes (i 10)                     ; Fill array.
           (setf (deref ar i) i))
         (setf (slot c-struct 'x) 20)
         (setf (slot c-struct 's) "a Lisp string")
 
         (with-alien ((res (* (struct c-struct))
                           (c-function 5 "another Lisp string" (addr c-struct) ar)))
           (format t "~&amp;back from C function~%")
           (multiple-value-prog1
               (values (slot res 'x)
                       (slot res 's))
 
             ;; Deallocate result. (after we are done referring to it:
             ;; "Pillage, *then* burn.")
             (free-alien res)))))

To execute the above example, it is necessary to compile the C routine, e.g.: `cc -c test.c && ld -shared -o test.so test.o' (In order to enable incremental loading with some linkers, you may need to say `cc -G 0 -c test.c')

Once the C code has been compiled, you can start up Lisp and load it in: `sbcl'. Lisp should start up with its normal prompt.

Within Lisp, compile the Lisp file. (This step can be done separately. You don't have to recompile every time.) `(compile-file “test.lisp”)'

Within Lisp, load the foreign object file to define the necessary symbols: `(load-shared-object “test.so”)'.

Now you can load the compiled Lisp (“fasl”) file into Lisp: `(load “test.fasl”)' And once the Lisp file is loaded, you can call the Lisp routine that sets up the parameters and calls the C function: `(test-c-call::call-cfun)'

The C routine should print the following information to standard output:

     i = 5
     s = another Lisp string
     r->x = 20
     r->s = a 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.

After return from the C function, the Lisp wrapper function should print the following output:

     back from C function

And upon return from the Lisp wrapper function, before the next prompt is printed, the Lisp read-eval-print loop should print the following return values:

     10
     "a C string"
 
 
sbcl/ffi.txt · Last modified: 2010/01/09 02:23 by admin
topheader
© 1998-2021, SciOS Scientific Operating Systems GmbH

Legal | Credits | Profile | Contact | Customer Login