11. Foreign Function Interface

The STklos Foreign Function Interface (FFI for short) has been defined to allow an easy access to functions written in C without needing to build C-wrappers and, consequently, without any need to write C code. FFI is very machine dependent and STklos uses the libffi library, a portable Foreign Function Interface library, to give access to C written code. This library supports a large set of architectures/OS that are described on its Home Page.

Moreover, since FFI allows very low level access, it is easy to crash the STklos VM when using an external C function.

Note that the support for FFI is still minimal and that it will evolve in future versions.

11.1. External functions

The definition of an external function is done with the define-external special form. This form takes as arguments a typed list of parameters and accepts several options to define the name of the function in the C world, the library which defines this function, …​ The type of the function result and the type of its arguments are defined in Table 1. This table lists the various keywords reserved for denoting types and their equivalence between the C and the Scheme worlds.

Table 1. FFI types
Name Corresponding C type Corresponding Scheme type

:void

void

none

:char

char

Scheme character

:short

short

Scheme integer

:ushort

unsigned short

Scheme integer

:int

int

Scheme integer

:uint

unsigned int

Scheme integer

:long

long int

Scheme integer

:ulong

unsigned long int

Scheme integer

:float

float

Scheme real number

:double

double

Scheme real number

:boolean

int

boolean

:pointer

void *

Scheme pointer object or Scheme string

:string

char *

Scheme string

:obj

void *

Any Scheme object passed as is

STklos syntax

(define-external name parameters option)

The form define-external binds a new procedure to name. The arity of this new procedure is defined by the typed list of parameters given by parameters. This parameters list is a list of keywords (as defined in the previous table) or couples whose first element is the name of the parameter, and the second one is a type keyword. All the types defined in the above table, except :void, are allowed for the parameters of a foreign function.

Define-external accepts several options:

  • :return-type is used to define the type of the value returned by the foreign function. The type returned must be chosen in the types specified in the table. For instance:

    (define-external maximum(:int :int)
       :return-type :int)

    defines the foreign function maximum which takes two C integers and returns an integer result. Omitting this option default to a result type equal to :void (i.e. the returned value is undefined).

  • :entry-name is used to specify the name of the foreign function in the C world. If this option is omitted, the entry-name is supposed to be name. For instance:

    (define-external minimum((a :int) (b :int))
       :return-type :int
       :entry-name  "min")

    defines the Scheme function minimum whose application executes the C function called min.

  • :library-name is used to specify the library which contains the foreign-function. If necessary, the library is loaded before calling the C function. So,

    (define-external minimum((a :int) (b :int))
       :return-type  :int
       :entry-name   "min"
       :library-name "libminmax")

    defines a function which will execute the function min located in the library libminmax.xx (where xx is the suffix used for shared libraries on the running system (generally so)).

Hereafter, are some commented definitions of external functions:

 (define-external isatty ((fd :int))
     :return-type :boolean)

  (define-external system ((cmd :string))
    :return-type :int)

  (define-external ttyname (:int)
    :return-type :string)

All these functions are defined in the C standard library, hence it is not necessary to specify the :library-name option.

  • istty is declared here as a function which takes an integer and returns a boolean (in fact, the value returned by the C function isatty is an int, but we ask here to the FFI system to translate this result as a boolean value in the Scheme world).

  • system is a function which takes a string as parameter and returns an int.

  • ttyname is a function whih takes an int and returns a string. Note that in this function the name of the parameter has been omitted as within C prototypes.

If an external function receives an :int argument and is passed a Scheme bignum, which then doesn’t fit a long int in C, the external function will signal an error. When a :float or :double argument is declared and is passed a Scheme real that requires so many bits so as to not be representable in that type, that argument will be silently taken as infinity.

 (define-external c-abs ((fd :int))
     :entry-name "abs"
     :return-type :int)

 (define-external c-fabs ((fd :double))
     :entry-name "fabs"
     :return-type :double)

 (define-external c-fabsf ((fd :float))
     :entry-name "fabsf"
     :return-type :float)

We can now use the function we have just defined:

(c-abs (- (expt 2 70)))    => Error
(c-fabs -1.0e+250)         => 1e+250
(c-fabsf -1.0e+250)        => +inf.0
(c-fabs (- (expt 10 300))) => 1e+300
(c-fabs (- (expt 10 600))) => +inf.0

In the following example we use the :string type. C functions accepting pointers to null-terminated strings are directly translated to this type.

The POSIX function strpbrk accepts two string arguments (in C, two pointers to char). The C call strpbrk(str1, str2) returns a pointer to the first occurrence in the string str1 of one of the bytes in the string str2.

(define-external c-strpbrk ((str :string) (accept :string))
    :entry-name "strpbrk"
    :return-type :string)
(c-strpbrk "a string" "rz") => "ring"

Note that it would be possible to use a :pointer type instead for the return value, although in this case it would be more cumbersome (but does help understand the FFI better!):

(define-external c-strpbrk ((str :string) (accept :string))
    :entry-name "strpbrk"
    :return-type :pointer)
(c-strpbrk "a string" "rz")
                            => #[C-pointer 7f17baf94d06 @ 7f17bafb4360]
(cpointer->string (c-strpbrk "a string" "rz"))
                            => "ring"

In order to pass a NULL pointer to an external C function, the #void Scheme value can be used.

As an example, the second argument to the strtol function can be NULL:

(define-external strtol(:string :pointer :int)
  :return-type :long)
(strtol "1001" #void 10) => 1001
(strtol "1001" #void 2)  => 9

Functions on C pointers are described in the next section.

11.2. C pointers

It is very common that external functions return pointers, serving as handles on internal structures. This pointers, called hereafter cpointers, are then boxed in a Scheme objects. This section presents the functions that can be used to deal with C pointers.

Note that by using cpointers objects, one gives up the safety of the Scheme environment, and care must be taken to avoid memory corruptions, errors, crashes…​

STklos procedure

(cpointer? obj)

Returns #t is obj is a cpointer (a Scheme object which encapsulate a pointer to a C object), and #f otherwise.

STklos procedure

(cpointer-null? obj)

Returns #t is obj is a cpointer and its value is the C NULL value. Returnd #f otherwise.

STklos procedure

(cpointer-data obj)
(cpointer-data-set! obj adr)

cpointer-data returns the value associated to cpointer obj (that is the value of the pointer itself: an address).

cpointer-data-set! permits to change the pointer stored in the obj cpointer to adr. This is of course very dangerous and could lead to fatal errors.

STklos procedure

(cpointer-type obj)
(cpointer-type-set! obj tag)

cpointer-type returns the tag type associated to a cpointer. The C runtime or an extension can associate * a tag to a cpointer to make some controls (for instance, verify that obj is a cpointer on a widget structure). This function returns void if a type has not been set before. The semantic associated to this tag is completely left to the extension writer.

cpointer-type-set! permits to set the tag of the obj cpointer to tag (which can be of any type).

STklos procedure

(cpointer→string str)

Returns the C (null terminated) string str as a Scheme string. If str doesn’t contain a C string, the result will probably result in a fatal error.

(define-external c-ghn ((s :pointer) (size :int))
                 :entry-name "gethostname"
                 :return-type :int)
(define name (allocate-bytes 10))

name                    => #[C-pointer 7fd830820f80 @ 7fd8305bee40]
(c-ghn name 9)          => 0
(cpointer->string name) => "socrates"

STklos procedure

(allocate-bytes n)

Allocate-bytes will allocate n consecutive bytes using the standard STklos allocation function (which uses the Boehm–Demers–Weiser garbage collector [BoehmGC]). It returns a cpointer Scheme object that points to the first byte allocated. This pointer is managed by the standard GC and doesn’t need to be freed.

STklos procedure

(free-bytes obj)

Obj must be a cpointer to allocated data. When Free-bytes is called on obj, it will deallocate its data calling - the C function free (if it was allocated by the standard C malloc function), or - the Boehm GC free function (if the pointer was allocated using allocate-bytes primitive).

(define a (allocate-bytes 10))
a   => #[C-pointer 7fd91e8e0f80 @ 7fd91e897b70]
(cpointer-type-set! a 'myadress)
a   => #[myadress-pointer 7fd91e8e0f80 @ 7fd91e897b70]
(free-bytes a)
a   => #[myadress-pointer 0 @ 7fd91e897b70]

After the call to free-bytes, when a is printed, the first number shown is zero, indicating that its data pointer does not point to allocated memory (a NULL value for C).


1. Documentation about hygienic macros has been stolen in the SLIB manual
1. In fact define-module on a given name defines a new module only the first time it is invoked on this name. By this way, interactively reloading a module does not define a new entity, and the other modules which use it are not altered.
2. This transcript uses the default toplevel loop which displays the name of the current module in the evaluator prompt.
1. Under Unix, you can simply connect to a listening socket with the telnet of netcat command. For the given example, this can be achieved with netcat localhost 12345
2. Port 13, if open, can be used for testing: making a connection to it permits to know the distant system’s idea of the time of day.
1. The "pattern matching compiler" has been written by Jean-Marie Geffroy and is part of the Manuel Serrano’s Bigloo compiler since several years [Bigloo]
1. This section is an adaptation of Jeff Dalton’s (J.Dalton@ed.ac.uk) "Brief introduction to CLOS" which can be found at http://www.aiai.ed.ac.uk/~jeff/clos-guide.html