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-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 bename
. 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 calledmin
. -
: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 librarylibminmax.xx
(wherexx
is 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).