FFI Helper User Guide Matt Wette September 2026
This is a user guide for the NYACC FFI Helper.
The acronym FFI stands for “Foreign Function Interface”. It refers to
Guile’s facility for binding functions and variables from C-source libraries
into Guile programs. This distribution provides utilities for generating a
loadable Guile module from a set of C declarations and associated libraries.
The C declarations can, and conventionally do, come from naming a set of C
include files. The nominal method for use is to write a ffi-module
specification in a file which includes a define-ffi-module declaration,
and then use the command guild compile-ffi to produce an associated
file of Guile Scheme code. For example,
$ guild compile-ffi ffi/cairo.ffi wrote `ffi/cairo.scm'
The ffi-helper (FH) does not generate C code. The hooks to access functions
in the Cairo library are provided in 100% Guile Scheme via (system
foreign) and (system foreign-library).
Since version 2.00, the FFI Helper uses it’s own backend called cdata
for using bytevectors to handle C data. Since version 4.0 the cdata module
is distributed separately, since it is the only non-Guile module needed for
runtime execution of FH-generated code. In previous versions, the module
(bytestructures guile) was used. An experimental bytestructures
implementation is available by passing
-b nyacc/ffi-bkend/bytestructures to
guild compile-ffi, or by setting the
environment variable
FFI_HELP_BACKEND=nyacc/ffi-bkend/bytestructures. The bytestructures
backend uses the scheme-bytestructures package, available from
https://github.com/TaylanUB/scheme-bytestructures. Releases
are available at
https://github.com/TaylanUB/scheme-bytestructures/releases.
In addition experimental backend support for “bstructs” is available by
passing the argument -b nyacc/ffi-bkend/bstructs.
In this manual we only discuss use of the default cdata
backend implementation.
To generate Guile Scheme for smaller C code units one can
write a ffi-module with the #:api-code or import the
ffi-help module an use the functions load-include-file,
ccode->sexp. The latter functions are exhaustively.
In addition the module (nyacc c99 ffi-help) exports a procedure
ccode->sexp to convert C code snippets as a string into associated
Scheme s-expressions that could be evaluated to implement the api.
The compiler for the FFI Helper (FH) is based on the C parser and utilities which are included in this NYACC package. Within the NYACC distribution, there are a number of example dot-ffi files in the directory examples/ffi.
At runtime, after the FFI Helper has been used to create Scheme code,
the modules (nyacc foreign cdata) and
(nyacc foreign arch-info) are required. No other code from the
NYACC distribution is needed. However, note that the process of
creating the Scheme
output depends on reading system headers, so the generated code may
well contain operating system and machine dependencies. If you copy
code to a new machine, you should re-run guild compile-ffi.
You are probably hoping to see an example, so let’s try one.
This is a small FH example to illustrate its use. We will start with the Cairo package because that is the first one I started with in developing the FFI Helper. Say you are an avid Guile user and want to be able to use Cairo in Guile. On most systems Cairo comes with the associated pkg-config support files; this demo depends on that support.
If you want to follow along and are working in the distribution tree, you should source the file env.sh in the examples directory.
By practice, I like to put all FH generated modules under a directory called ffi/, so we will do that. We start by generating, in the ffi directory, a file named cairo.ffi with the following contents:
(define-ffi-module (ffi cairo)
#:pkg-config "cairo"
#:include '("cairo.h"))
To generate a Guile module you execute guild as follows:
$ guild compile-ffi ffi/cairo.ffi compiling `ffi/cairo.ffi' ... ... wrote `ffi/cairo.scm' compiling `ffi/cairo.scm' ... ... wrote `cairo.scm.go'
Though the file cairo/cairo.ffi is only three lines long, the file ffi/cairo.scm will be over five thousand lines long. It looks something like the following:
(define-module (ffi cairo)
#:use-module (system foreign-library)
#:use-module ((system foreign) #:prefix ffi:))
(begin
(use-modules (foreign cdata))
(define arg->number cdata-arg->number)
(define arg->pointer cdata-arg->pointer)
(define (extern-ref obj) (cdata-sel obj '*))
(define (extern-set! obj val) (cdata-set! obj '* val)))
...
;; extern int cairo_version(void);
(define cairo_version
(let ((~proc (delay (ffi:pointer->procedure
ffi:int
(foreign-pointer-search "cairo_version")
(list)))))
(lambda () (let () ((force ~proc))))))
(export cairo_version)
...
(define cairo_matrix_t
(name-ctype
'cairo_matrix_t
(cstruct
(list `(xx ,(cbase 'double))
`(yx ,(cbase 'double))
`(xy ,(cbase 'double))
`(yy ,(cbase 'double))
`(x0 ,(cbase 'double))
`(y0 ,(cbase 'double))))))
(define-public cairo_matrix_t*
(name-ctype
'cairo_matrix_t*
(cpointer cairo_matrix_t)))
... many, many more declarations ...
;; access to enum symbols and #define'd constants:
(define ffi-cairo-symbol-tab
'((CAIRO_SVG_UNIT_PERCENT . 9)
(CAIRO_SVG_UNIT_PC . 8)
(CAIRO_SVG_UNIT_PT . 7)
... more constants ...
))
(define ffi-cairo-symbol-val
(lambda (k) (or (assq-ref ffi-cairo-symbol-tab k))))
(export ffi-cairo-symbol-val)
...
Note that from the pkg-config spec the FH compiler picks up the
required libraries to bind in. Also, #define based constants,
as well as those defined by enums, are provided in a lookup function
ffi-cairo-symbol-val. So, for example
guile> (use-modules (ffi cairo)) guile> (ffi-cairo-symbol-val 'CAIRO_FORMAT_ARGB32)) $1 = 0
Let’s try something more useful: a real program. Create the following
code in a file, say cairo-demo.scm, then fire up a Guile session
and load the file.
(use-modules (ffi cairo)) (define srf (cairo_image_surface_create 'CAIRO_FORMAT_ARGB32 200 200)) (define cr (cairo_create srf)) (cairo_move_to cr 10.0 10.0) (cairo_line_to cr 190.0 10.0) (cairo_line_to cr 190.0 190.0) (cairo_line_to cr 10.0 190.0) (cairo_line_to cr 10.0 10.0) (cairo_stroke cr) (cairo_surface_write_to_png srf "cairo-demo.png") (cairo_destroy cr) (cairo_surface_destroy srf)
guile> (load "cairo-demo.scm") ... ;;; compiled /.../cairo-demo.scm.go guile>
If we set up everything correctly we should have generared the target
file cairo-demo.png which contains the image of a square. A
few items in the above code are notable. First, the call to
cairo_image_surface_create accepted a symbolic form
'CAIRO_FORMAT_ARGB32 for the format argument. It would have
also accepted the associated constant 0. In addition,
procedures declared in (ffi cairo) will accept Scheme strings
where the C function wants “pointer to string.”
Now try this in your Guile session:
guile> srf $4 = #<cdata cairo_surface_t* 0x7fda53e01880> guile> cr $5 = #<cdata cairo_t* 0x7fda54828800>
Note that the FH keeps track of the C type names you use. This can be useful for debugging (at a potential cost of bloating the namespace). The constants you see are the pointer values. But it goes further. Let’s generate a matrix type:
guile> (use-modules (foreign cdata)) guile> (define m (make-cdata cairo_matrix_t)) guile> m $6 = #<cdata cairo_matrix_t 0x7056028777a0> guile> (cdata& m) $7 = #<cdata pointer 0x7055f7da7b30>
When it comes to C APIs that expect the user to allocate memory for a structure and pass the pointer address to the C function, FH provides a solution:
guile> (cairo_get_matrix cr (cdata& m)) guile> (cdata-ref m 'xx) $8 = 1.0
But the FFI helper can also be used on a per declaration basis, but you must first import the proper modules and libraries.
The following example shows how to convert to scheme code using
the procedure ccode->sexp:
guile> (use-modules (nyacc lang c99 ffi-help))
guile> (define sx (ccode->sexp "struct foo { int x; };"))
guile> ,pp sx
$4 = (begin
(define struct-foo
(name-ctype
'struct-foo
(cstruct (list `(x ,(cbase 'int))))))
(define-public struct-foo*
(name-ctype 'struct-foo* (cpointer struct-foo)))
(exprot struct-foo stuct-foo*))
guile> (eval sx (current-module))
guile> struct-foo
$5 = #<ctype struct struct-foo 0x73af1fc95480>
This document needs an explanation that wrapped C functions that return structs or unions will instead pass ‘cdata’ data for pointers to those types.
This document needs an explanation of the backend capability.
Wrong type argument in position 1 (expecting PRIMITIVE_P): #<procedure 7fed1234 (_ _ _ _)>
This typically indicates that a lambda form passed to a ffi-data procedure.
Guile has an API, called the Foreign Function Interface, which allows
one to avoid writing and compiling C wrapper code in order to access C
coded libraries. The API is based on libffi and is covered in
the Guile Reference Manual. We review some important bits here. For
more insight you should read the relevant sections in the Guile
Reference Manual. For more info on libffi internals visit
libffi.
The relevant procedures used by the FH are
foreign-library-pointergenerates Scheme-level pointer to a C function or data
pointer->proceduregeneates a Scheme lambda given C function signature
dynamic-pointerprovides access to global C variables
string->pointerconverts a Scheme string to a Guile pointer
pointer->stringconverts Guile pointer for C string to a Scheme string
Several of the above require import one or both of the modules
(system foreign) and (system foreign-library.
In order to generate a Guile procedure wrapper for a function, say
int foo(char *str), in some foreign library, say
libbar.so, you can use something like the following:
(use-modules (system foreign))
(define foo (pointer->procedure
int
(foreign-library-pointer "foo" "libbar")
(list '*)))
The argument int is a variable name for the return type,
the next argument is an expression for the function pointer and the
third argument is an expression for the function argument list.
To execute the function, which expects a C string, you use something like
(define result-code (foo (string->pointer "hello")))
If you want to try a real example, this should work:
guile> (use-modules (system foreign))
guile> (define strlen
(pointer->procedure
int (dynamic-func "strlen" (dynamic-link)) (list '*)))
guile> (strlen (string->pointer "hello, world"))
$1 = 12
It is important to realize that internally Guile takes care of converting Scheme arguments to and from C types. Scheme does not have the same type system as C and the Guile FFI is somewhat forgiving here. When we declare a C function interface with, say, an uint32 argument type, in Scheme you can pass an exact numeric integer. The FH attempts to be even more forgiving, allowing one to pass symbols where C enums (i.e., integers) are expected.
As mentioned, access to libraries not compiled into Guile is
accomplished via load-foreign-library. (FFI-modules
typically use pkg-config to locate includes and get a list
of libraries. Since a particular C function might be in one of many
libraries compiled ffi modules will search through all of them
to find the necessary binding.)
The cdata module ((foreign cdata) and its
partner arch-info ((foreign arch-info) provide a
way to work with data originating from C libraries.
Size and alignment is
tracked for all types. Types are classified into the following kinds:
base, struct, union, array, pointer, enum and function. The procedures
cbase, cstruct, cunion, cpointer,
carray, cenum and cfunction generate ctype
objects, and the procedure make-cdata will generate data
objects based on these. The underlying bits of data are stored in Scheme
bytevectors. Access to component data is provided by the
cdata-ref procedure and mutation is
accomplished via the cdata-set! procedure.
The modules support non-native machine architectures via the
global parameter *arch*.
The following are the basic procedures used:
Given symbolic name, generate a base ctype. The name can
be something like unsigned, double, or can be a
cdata machine type like u64le. For example,
(define double-type (cbase 'double))
There is a pseudo-type void.
Generate a C pointer type for type. To reference or de-reference
cdata object see cdata& and cdata*. type can be
the symbol void or a symbolic name used as argument to cbase.
(define foo_t (cbase 'int)) (cpointer (delay foo_t))
Construct a struct ctype with given fields. If packed,
#f by default, is #t, create a packed structure.
fields is a list with entries of the form (name type) or
(name type lenth) where name is a symbol or #f
(for anonymous structs and unions), type is a <ctype>
object or a symbol for a base type and length is the length
of the associated bitfield.
Construct a ctype union type with given fields. See cstruct for a description of the fields argument.
Create an array of type with length.
If length is zero, the array length is unbounded: it’s length
can be specified as argument to make-cdata.
enum-list is a list of name or name-value pairs
(cenum '((a 1) b (c 4))
If packed is #t the size will be smallest that can hold it,
as if defined in C with __attribute__((packed)).
Generate a C function type to be used with cpointer. The arguments
proc->ptr and ptr->proc are procedures that convert a
procedure to a pointer, and pointer to procedure, respectively. The
optional argument #:variadic, if #t, indicates the function
uses variadic arguments. For this case (I need to add documention).
Here is an example:
(define (f-proc->ptr proc) (ffi:procedure->pointer ffi:void proc (list))) (define (f-ptr->proc fptr) (ffi:pointer->procedure ffi:void fptr (list))) (define ftype (cpointer (cfunction f-proc->ptr f-ptr->proc)))
The thinking here is that a cfunction type is a proxy for a
C function in memory, with a getter and setter to read from or write
to memory.
Generate a cdata object of type type with optional value. If value is not provided, the object is zeroed. As a special case, a positive integer arg to a zero-sized array type will allocate storage for that many items, associating it with an array type of that size.
Make a cdata object from a pointer. That is, instead of creating a
bytevector to hold the data use the memory at the pointer using
pointer->bytevector.
Return the Scheme (scalar) slot value for selected tag ... with respect to the cdata object data.
(cdata-ref my-struct-value 'a 'b 'c))
This procedure returns Guile values for cdata kinds base,
pointer, procedure, array (an array) and struct
(an alist). For union an exception is raised. The returned values
are freshly allocated copies. If want a cdata object, use cdata-sel.
Set slot for selcted tag ... with respect to cdata data to value. Example:
(cdata-set! my-struct-data 42 'a 'b 'c))
If value is a <cdata> object then copy that (if types match).
The value argument can be a Scheme procedure when the associated
ctype is a pointer to function.
Values accepted by cdata-set! and make-cdata are as follows,
based on the cdata target ctype.
In addition, if the value is of ctype, a copy of the underlying bytevector
contents will be performed. An error will be thrown if the types are not equal.
Also, if the value argument to make-cdata is an integer and
the type argument is an array of length zero, then space is allocated to
accomodate that length array. Here are some examples:
> (define tri1-t (carray (carray (cbase 'double) 2) 3)) > (define tval1 (make-cdata tri1-t #2f64((1.0 2.0) (3.0 4.0) (5.0 6.0)))) > (cdata-ref tval1) $1 = #2f64((1.0 2.0) (3.0 4.0) (5.0 6.0)) > (cdata-set! tval1 1.5 0 1) > (cdata-ref tval1) $2 = #2f64((1.0 1.5) (3.0 4.0) (5.0 6.0)) >
and
> (define loc-t (cstruct `((x double) (y double))) 3)
> (define tri2-t (carray loc-t 3))
> (define tval2 (make-cdata tri2-t (vector '((x . 1.0) (y . 2.0))
'((x . 3.0) (y . 4.0))
'((x . 2.0) (y . 5.0)))))
> tval2
$3 = #<cdata array 0x79ae9a077200>
> (cdata-ref tval2)
$4 = #(((x . 1.0) (y . 2.0)) ((x . 3.0) (y . 4.0)) ((x . 2.0) (y . 5.0)))
> (cdata-set! tval2 1.5 0 'x)
> (cdata-ref tval2)
$5 = #(((x . 1.5) (y . 2.0)) ((x . 3.0) (y . 4.0)) ((x . 2.0) (y . 5.0)))
Generate a reference (i.e., cpointer) to the contents in the underlying bytevector.
De-reference a pointer. Returns a cdata object representing the contents at the address in the underlying bytevector.
In this section we hope to provide some insight into the FH works. The FH specification, via the dot-ffi file, determines the set of declarations which will be included in the target Guile module. If there is no declartion filter, then all the declarations from the specified set of include files are targeted. With the use of a declaration filter, this set can be reduced. By declaration we mean typedefs, aggregate definitions (i.e., structs and unions), function declarations, and external variables.
In the C language a typedef does not declare a new type, but an alias, so
there is no harm in expanding typedefs which appear outside the specification.
For example, say the file foo.h includes a declaration for the typedef
foo_t and the file bar.h includes a declaration for the typedef
bar_t. Furthermore, suppose foo_t is a struct that references
bar_t. Then the FH will preserve the typedef foo_t but expand
bar_t. That is, if the declarations are
typedef int bar_t; /* from bar.h */
typedef struct { bar_t x; double y; } foo_t; /* from foo.h */
then the FH will treat foo_t as if it had been declared as
typedef struct { int x; double y; } foo_t; /* from foo.h */
One of the challenges in automating C-Scheme type conversion is that C
code nominally uses pointer to type alot. So, as the FH generates types for
aggregates, it will automatically generate types for associated
pointers. For example, in the case above with foo_t the FH will
generate an aggregate type named foo_t and a pointer type named
foo_t*. When the FH sees a pointer to type declaration in C
it makes an attempt to use the declared foo_t* type instead of
(cpointer foo_t). This makes the task of generating an object value in
Scheme, and then passing the pointer to that value as an argument to a
FFI-generated procedure, easy. The inverse operation (cdata* f1*
is also provided. Note that sometimes the C code needs to work with
pointer pointer types.
TODO: More on the design at a later time.
(nyacc lang c99 ffi-help) ¶(define ffi-module module-name ...)
*#:pkg-config *#:include *#:inc-filter *#:decl-filter *#:api-code *#:library *#:cpp-defs
*#:inc-dirs *#:lib-dirs #:renamer #:use-ffi-module
#:def-keepers undocumented
#:pkg-configThis option take a single string argument which provides the name used
for the pkg-config program. Try man pkg-config.
#:includeThis form, with expression argument, indicates the list of include
files to be processed at the top level. Without use of the
#:inc-filter form, only declarations in these files will be
output. To constrain the set of declarations output use the
#:decl-filter form.
#:inc-filterThis form, with predicate procedure argument taking the form
(proc file-spec path-spec), is used to indicate which includes
beyond the top-level should have processed declarations emitted in the
output. The file-spec argument is a string as parsed from
#include statements in the C code, including brackets or double
quotes (e.g., "<stdio.h>", "\"foo.h\""). The
path-spec is the full path to the file.
#:decl-filterThis form, with a predicate procedure argument, is used to restrict
which declarations should be processed for output. The single
argument is either a string or a pair. The string form is used for
simple identifiers and the pair is used for struct, union and enum
forms from the C code (e.g., (struct . "foo")).
#:use-ffi-moduleThis form, with literal module-type argument (e.g., (ffi
glib)), indicates dependency on declarations from another processed
ffi module. For example, the ffi-module for (ffi gobject)
includes the form #:use-ffi-module (ffi glib).
#:libraryThis form, with a list of strings, indicates which (shared object)
libraries need to be loaded. The formmat of each string in the list
should be as provided to the dynamic-link form in Guile.
#:renamerThe argument is a procedure called as (proc name ctxt) where
name is a string for the name being translated and ctxt is
the context as a symbol, one of the following:
'fieldfield in a struct or union
'enumname of an enum
'typename of a typedef, struct, union or enum definition
'functionname of a function
'variablename of an extern variable
#:cpp-defsThis form, with a list of strings, provides extra C preprodessor
definitions to be used in processing the header files. The defines
take the form "SYM=val".
#:inc-dirsThis form, with a list of strings, provides extra directories in which to search for include files.
#:lib-dirsThis form, with a list of strings, provides extra directories in which to search for libraries.
#:api-codeThis, in lieu of using #:include is a string of C code that will
be translated. In the string of code, any #include based code
will not be translated unless explicitly directed so via the
#inc-filter option.
#:def-keepersThis form, with a list of strings, provides extra (non-function) C preprocessor macro definitions that should be included in the output.
Work to go here:
This is the functionality that Ludo was asking for: to be at guile prompt and be able to issue
(use-modules (nyacc lang c99 ffi-help)) (load-include-file "cairo.h" #:pkg-config "cairo")
guile> ,use (nyacc lang c99 ffi-help) guile> (load-include-file "cairo.h" #:pkg-config "cairo") ;; wait a while guile> ...
Since this is not all straightforward you will get errors.
Method
After using the FFI Helper to provide code for some packages you may
notice that the quantity of code produced is large. For example, to
generate a guile interface for gtk2+, along with glib, gobject, pango
and gdk you will end up with over 100k lines of scm code. This may
seem bulky. Instead it may be preferable to generate a small number
of calls for gtk and work from there. In order to achieve this you
could use the #:api-code or #:decl-filter options.
For example, in the expansion of the GLU/GL FFI module, called
glugl.ffi, I found that a very large number of declarations
starting with PF were being generated. I removed these using
the #:decl-filter option:
(define-ffi-module (ffi glugl)
#:include '("GL/gl.h" "GL/glu.h")
#:library '("libGLU" "libGL")
#:inc-filter (lambda (spec path) (string-contains path "GL/" 0))
#:decl-filter (lambda (n) (not (and (string? n) (string-prefix? "PF" n)))))
Using the option reduced glugl.scm from 59,274 lines down to 15,354 lines.
As another example, if we wanted to just generate code for the gtk hello world demo we could write
(define-ffi-module (hack1)
#:pkg-config "gtk+-2.0"
#:api-code "
#include <gtk2.h>
void gtk_init(int *argc, char ***argv);
void gtk_container_set_border_width(GtkContainer *container,
guint border_width);
void gtk_container_add(GtkContainer *container, GtkWidget *widget);
void gtk_widget_show(GtkWidget *widget);
void gtk_main(void);
")
Since the above example does not ask the FH to pull in typedef’s then the pointer types will be expanded to native. You could invent your own types or echo the typedefs from the package headers
$ ./configure $ make $ make check $ make install
Please report bugs by navigating with your browser to
‘https://github.com.org/mwette/nyacc/issues’.
Copyright (C) 2017-2026 – Matthew Wette.
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included with the distribution as COPYING.DOC.