I’ve seen several people ask for a tutorial on Racket’s foreign function interface (FFI), which allows you to dynamically load C libraries for use in Racket code. While I think the documentation for the FFI is quite good, it is a lot of information to process and the overview examples may be tricky to run for a beginner.
With that in mind, this blog post will provide a step-by-step tutorial for Racket’s FFI that requires minimal setup. All that you will need to follow along is a copy of Racket and ideally a DrRacket window.
Before getting into the details, I wanted to note that the FFI library is based on the work of Eli Barzilay and Dmitry Orlovsky. They have a Scheme Workshop paper that you can read if you’re curious about the design.
The tutorial will focus on using the Cairo graphics library, mainly because it comes bundled with Racket.
To start, let’s aim to reproduce the output of the "multi segment caps" C sample code on Cairo’s samples page:
|cairo_move_to (cr, 50.0, 75.0);|
|cairo_line_to (cr, 200.0, 75.0);|
|cairo_move_to (cr, 50.0, 125.0);|
|cairo_line_to (cr, 200.0, 125.0);|
|cairo_move_to (cr, 50.0, 175.0);|
|cairo_line_to (cr, 200.0, 175.0);|
|cairo_set_line_width (cr, 30.0);|
|cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);|
In order to actually draw this example to the screen, we will need a Cairo surface to draw on. So here is some boilerplate for you to execute before we actually play with the FFI:
|> (require racket/draw)|
|> (define bt (make-bitmap 256 256))|
|> (define bt-surface (send bt get-handle))|
This uses the Racket drawing library racket/draw to construct a bitmap object that we’ll draw on. The get-handle method just extracts a low-level Cairo surface value that we can use.
NB: these code snippets don’t come with a #lang declaration because they simulate interactions at the REPL/interaction area in DrRacket. When following along, just copy & paste the snippets into your REPL.
Our first real step is to import the FFI itself:
|> (require ffi/unsafe)|
As the module name suggests, the FFI is unsafe and can cause your Racket process to segfault. If you’re following along in DrRacket, you will want to save your file frequently.
Next, we can load the Cairo library to obtain a foreign-library value, which is a handle that we use to access C values and functions:
Since Cairo has already been loaded by the Racket process because of the racket/gui import earlier, we can supply #f here as an argument to ffi-lib. Normally you supply the name of a shared library file such as "libcairo":
The last list argument specifies the accepted versions (#f allows a version-less library). For this post, those details aren’t important but see the docs on ffi-lib if you’re curious.
The name of the value as a string (or symbol or bytestring)
a foreign library value, and
a C type, which is a type description that tells the FFI how to marshall between Racket and C.
C types are a crucial concept for the FFI. They range from relatively simple types like _int and _pointer to more complicated type constructors such as _enum and _fun. As you probably noticed, C types are prefixed with an underscore by convention. You can also define your own types by calling make-ctype with two functions that handle marshalling between C and Racket code.
To make progress with our Cairo code, we need to create a drawing context from the surface object bt-surface that we defined a while ago. The relevant function in the Cairo docs is cairo_create, which has the following type signature:
|/* NB: this is C code */|
|cairo_t * cairo_create (cairo_surface_t *target);|
The following definition shows how to use this type to obtain a foreign function:
Then we can use cairo-create as an ordinary racket function:
|> (define ctx (cairo-create bt-surface))|
Before we move on to completing the Cairo sample, lets consider the safety of the C type we used again. Since we only specified _pointer types, it is easy to accidentally misuse the function:
|; You may not want to actually run this|
|; a cairo_t is not a cairo_surface_t|
|(cairo-create (cairo-create bt-surface))|
To prevent such bad uses, it is good practice to use tagged pointer types using define-cpointer-type. Here are two example definitions that correspond to the cairo_t and cairo_surface_t types from earlier:
|; The leading underscores are mandatory|
|> (define-cpointer-type _cairo_t)|
|> (define-cpointer-type _cairo_surface_t)|
We can then redefine cairo-create with a better type, which will prevent ill-typed calls:
|> (cairo-create (cairo-create bt-surface))|
cairo_surface_t->C: argument is not non-null
Unfortunately our old definition of ctx doesn’t have this tag:
|> (cpointer-has-tag? ctx 'cairo_t)|
Which means we will see errors if we try to use it in future interactions with the more precise C type. To get around this, it’s also possible to update existing pointers with a tag like this:
Executing the tag push above is necessary to get some of the following snippets to work (if you are following along step-by-step).
Now let’s start building the FFI bindings for the functions in the Cairo sample. First, let’s go ahead and look at all of the types for the sample functions from the C API docs:
|void cairo_move_to (cairo_t *cr, double x, double y);|
|void cairo_line_to (cairo_t *cr, double x, double y);|
|void cairo_set_line_width (cairo_t *cr, double width);|
|void cairo_set_line_cap (cairo_t *cr, cairo_line_cap_t line_cap);|
|void cairo_stroke (cairo_t *cr);|
Starting with cairo_move_to, we can set up a definition like we did with cairo_create before:
This starts to look awfully verbose once you start writing more of these definitions. Luckily, the FFI library comes with some definition forms in the ffi/unsafe/define library that help reduce the verbosity. Here’s an alternative definition of cairo-move-to using the define-ffi-definer form from ffi/unsafe/define.
|> (require ffi/unsafe/define)|
|> (define-ffi-definer define-cairo cairo-lib)|
As you can see, the define-ffi-definer form lets you define a new macro that lets you avoid writing the library value over and over. If you stick to using C-style identifiers with underscores (e.g., cairo_move_to) you also don’t need to supply the C name either.
The definitions for cairo_line_to, cairo_set_line_width, and cairo_stroke aren’t very interesting, so I’ll just include them below without comment:
The cairo_set_line_cap case is more interesting because the type
cairo_line_cap_t is a C enumeration type. Racket’s FFI comes with
convenience forms for defining enumeration types—
Our design follows a simple principle: keep C-level functionality to a minimum.
and specifically about enumerations:
For example, the C level part of our interface does not commit to a specific implementation for enumerations — it simply exposes C integers.
To define an enumeration, we can use the _enum form. This procedure sets up a new C type which converts between Racket symbols and the underlying integer representations. For the cairo_line_cap_t type, it suffices to just supply the cases as a list of symbols:
The exact symbols that we specify are not important, since they just map to integers anyway. The choice depends on what is convenient for the Racket interface. It’s also possible to specify how the symbols map to integers more precisely (see the docs on _enum for those details).
Given this type, we can specify the type for the line cap function:
Now that we have foreign function definitions for all of the relevant procedures, we can just transcribe the example from the beginning into Racket syntax:
|(cairo-move-to ctx 50.0 75.0)|
|(cairo-line-to ctx 200.0 75.0)|
|(cairo-move-to ctx 50.0 125.0)|
|(cairo-line-to ctx 200.0 125.0)|
|(cairo-move-to ctx 50.0 175.0)|
|(cairo-line-to ctx 200.0 175.0)|
|(cairo-set-line-width ctx 30.0)|
|(cairo-set-line-cap ctx 'round)|
Executing these procedure calls will draw into the Cairo surface we set up earlier, which is connected to our original Racket bitmap object bt. To see the results of what we drew, we can just evaluate bt at the REPL. But it’s a little nicer if we use the pict library to draw a frame around it to distinguish it from the background:
|> (require pict)|
|> (linewidth 2 (frame (bitmap bt)))|
And we’re done! Of course, there is a lot more to the FFI. For example, I haven’t covered how to handle C functions that return multiple results through pointer arguments. Or how to interoperate between Racket and C structs. I’m hoping to cover these in a future blog post, but in the meantime happy FFI hacking!