This FFI provides Scheme syntax for calling C functions and accessing C data. The functions and data structures are declared in a case sensitive .cdecl file, which is used by a shim generator to produce callout and callback trampoline functions. The trampolines are compiled and linked to the C toolkit, producing a shared object that Scheme can dynamically load.
Examples of the new syntax:
(malloc (C-sizeof "GdkEvent")) => #[alien 42 0x081afc60]
(C->= #@42 "GdkEvent any type" 14)
(C-> #@42 "GdkEvent any type") => 14
(C-enum "GdkEventType" 14) => |GDK_MAP|
(C-enum "GDK_MAP") => 14
(C-sizeof "GdkColor") => 12
(C-offset "GdkColor blue") => 8
(C-array-loc #@43 "GdkColor" (C-enum "GTK_STATE_NORMAL")) => #[alien 44 0x081afc60] ; New alien.
(C-array-loc! #@43 "GdkColor" (C-enum "GTK_STATE_PRELIGHT")) => #[alien 43 0x081afc78] ; Modified alien.
(C-call "gtk_window_new" retval args …) => #!unspecific
(C-callback "delete_event") => #[alien-function 44 Scm_delete_event]
(C-callback (lambda (window event) …)) => 13 ; A fixnum registration ID.
A Scheme-like declaration of a toolkit’s C functions, constants, and data types is given in a case sensitive .cdecl file. The C declarations look like this:
(extern (* GtkWidget) gtk_window_new (type GtkWindowType)) (typedef GtkWindowType (enum (GTK_WINDOW_TOPLEVEL) (GTK_WINDOW_POPUP)))
c-generate procedure reads these declarations and
writes three files: library-types.bin (a fasdump of the
parsed declarations), library-const.c (a C program that
prints C constants and struct offsets), and library-shim.c
(trampoline functions adapting Scheme procedure application to C
function call). The -const.c program generates a
-const.scm file, which can be expanded into a
(load-option 'FFI) (c-generate "prhello" "#include <gtk/gtk.h>")
The -types.bin and -const.bin files together provide
the information needed to expand
C-... syntax, and are only
needed at syntax time. The compiled -shim.so file is used at
run time, dynamically loaded into the Scheme machine. See Compiling and Linking, which describes these files in more detail, and shows
how they might be built and installed.
C-include syntax loads the -types.bin and
-const.bin files at syntax time. It should appear
at the top level of any file containing
C-... syntax, or be
expanded in the syntax environment of such code.
C-call syntax arranges to invoke a callout
trampoline. Arguments to the trampoline can be integers, floats,
strings (or bytevectors) or aliens (non-heap pointers, to C data
structures, see Alien Data). If a string argument might contain
non-ASCII characters (code points U+0080 and larger), it should be
converted to a bytevector e.g. by
string->utf8, else an error
could be signaled.
(let ((alien (make-alien '|GtkWidget|))) (C-call "gtk_window_new" alien type) (if (alien-null? alien) (error "could not open new window")) alien)
C-callback syntax is used when registering a
Scheme callback trampoline. The two forms of the syntax provide two
arguments for the registration function: the callback trampoline’s
address, and a “user data” argument. When the toolkit calls the
trampoline, it must provide the fixnum-sized user data as an argument.
(C-call "g_signal_connect" window "delete_event" (C-callback "delete_event") ; e.g. &Scm_delete_event (C-callback ; e.g. 314 (lambda (window event) (C-call "gtk_widget_destroy" window) 0)))
The first use of
C-callback (above) expands into a callback
trampoline address — an alien function. The second use evaluates to
a fixnum, which is associated with the given Scheme procedure.
C->= syntaxes peek and
poke values into alien data structures. They take an alien and a
constant string specifying the alien data type and the member to be
accessed (if any).
(C-> alien "GdkRectangle y") → (#[primitive c-peek-int] alien 4)
(C->= alien "GdkRectangle width" 0) → (#[primitive c-poke-int] alien 8 0)
(C-> alien "GdkEvent any type") → (#[primitive c-peek-int] alien 0)
(C-> alien "gfloat") → (#[primitive c-peek-float] alien 0)
A three argument form of the syntax provides an alien to receive a peeked pointer. This avoids consing a new alien.
(C-> alien "GtkWidget style" alien)
The above syntax is understood to say “The data at this
address is a GtkWidget. Load its
style member into
alien’s old address).”
C-offset syntaxes all
transform into integer constants. The last two transform into a padded
byte size and a byte offset respectively.
(C-enum "GTK_WINDOW_POPUP") → 1
(C-sizeof "GdkColor") → 12
(C-offset "GdkColor blue") → 8
The two element form of the
C-enum syntax can be used to find
the name of a constant given its runtime value. It expects the name
of an enum type in a constant string. If the runtime (second)
argument is not one of the constants declared by that type, the
returned value is
(C-enum "GdkEventType" (C-> #@42 "GdkEvent any type")) => |GDK_MAP|
syntaxes compute the locations of C array elements. They can be used
to advance a scan pointer or locate an element by its index. The
examples in the synopsis might expand as shown here.
(C-array-loc #@43 "GdkColor" (C-enum "GTK_STATE_NORMAL")) → (alien-byte-increment #@43 (* (C-sizeof "GdkColor") (C-enum "GTK_STATE_NORMAL"))) → (alien-byte-increment #@43 (* 12 0)) => #@44 (C-array-loc! #@43 "GdkColor" (C-enum "GTK_STATE_PRELIGHT")) → (alien-byte-increment! #@43 (* (C-sizeof "GdkColor") (C-enum "GTK_STATE_PRELIGHT"))) → (alien-byte-increment! #@43 (* 12 2)) => #@43
A simple scan of characters in the wide string
look like this.
(let ((len (C-> alien "toolkit_string_type int_member")) (scan (C-> alien "toolkit_string_type array_member"))) (let loop ((n 0)) (if (< n len) (let ((wchar (C-> scan "wchar"))) (process wchar) (C-array-loc! scan "wchar" 1) (loop (1+ n))))))
That is a quick look at the facilities. The next section describes the C declaration language, and the following sections examine the FFI’s syntax and runtime facilities in detail. Final sections provide an example program and show how its dynamically loaded shim is built.