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.
| 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
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-typeis 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-nameis used to specify the name of the foreign function in the C world. If this option is omitted, the entry-name is supposed to bename. For instance:(define-external minimum((a :int) (b :int)) :return-type :int :entry-name "min")defines the Scheme function
minimumwhose application executes the C function calledmin. -
:library-nameis 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
minlocated in the librarylibminmax.xx(wherexxis the suffix used for shared libraries on the running system (generallyso)).
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
Returns #t is obj is a cpointer (a Scheme object which encapsulate
a pointer to a C object), and #f otherwise.
STklos procedure
Returns #t is obj is a cpointer and its value is the C NULL value.
Returnd #f otherwise.
STklos procedure
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 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
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 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
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).