Tutorial: Racket FFI, Part 2
This is part 2 of my tutorial on using the Racket FFI. If you haven’t read part 1 yet, you can find it here. Update: part 3 is also now available here.
Part 2 will continue with more Cairo examples. In this installment, I plan to go over some more advanced FFI hacking such as handling computed argument values, custom return arguments, and using C structs.
First, here’s the core code from part 1 condensed and re-arranged into an example that you can copy and paste into your definitions area:
#lang racket |
(require racket/draw |
ffi/unsafe |
ffi/unsafe/define |
pict) |
; bitmap magic |
(define bt (make-bitmap 256 256)) |
(define bt-surface (send bt get-handle)) |
; C types |
(define-cpointer-type _cairo_t) |
(define-cpointer-type _cairo_surface_t) |
(define _cairo_line_cap_t |
(_enum '(butt round square))) |
(define cairo-lib (ffi-lib #f)) |
(define-ffi-definer define-cairo cairo-lib) |
; the foreign functions |
(define-cairo cairo-create |
(_fun _cairo_surface_t -> _cairo_t) |
#:c-id cairo_create) |
(define-cairo cairo-move-to |
(_fun _cairo_t _double _double -> _void) |
#:c-id cairo_move_to) |
(define-cairo cairo-line-to |
(_fun _cairo_t _double _double -> _void) |
#:c-id cairo_line_to) |
(define-cairo cairo-set-line-width |
(_fun _cairo_t _double -> _void) |
#:c-id cairo_set_line_width) |
(define-cairo cairo-stroke |
(_fun _cairo_t -> _void) |
#:c-id cairo_stroke) |
(define-cairo cairo-set-line-cap |
(_fun _cairo_t _cairo_line_cap_t -> _void) |
#:c-id cairo_set_line_cap) |
(define ctx (cairo-create bt-surface)) |
; Bitmap -> Pict |
; a helper for displaying the bitmap |
(define (show bt) |
(linewidth 2 (frame (bitmap bt)))) |
Dashes and array arguments
To start off, let’s look at another C example from the Cairo samples page. This time we will look at the "dash" example, which has a use of an input array:
double dashes[] = {50.0, /* ink */ |
10.0, /* skip */ |
10.0, /* ink */ |
10.0 /* skip*/ |
}; |
int ndash = sizeof (dashes)/sizeof(dashes[0]); |
double offset = -50.0; |
cairo_set_dash (cr, dashes, ndash, offset); |
cairo_set_line_width (cr, 10.0); |
cairo_move_to (cr, 128.0, 25.6); |
cairo_line_to (cr, 230.4, 230.4); |
cairo_rel_line_to (cr, -102.4, 0.0); |
cairo_curve_to (cr, 51.2, 230.4, 51.2, 128.0, 128.0, 128.0); |
cairo_stroke (cr); |
The most interesting function here is cairo_set_dash, which takes an array argument. The only other new functions are cairo_rel_line_to and cairo_curve_to which have very straightforward C types:
| |||||||
|
Meanwhile, the C type signature for cairo_set_dash from the Cairo docs looks like this:
void cairo_set_dash (cairo_t *cr, |
const double *dashes, |
int num_dashes, |
double offset); |
Something to note about the arguments is that num_dashes encodes the length of the array dashes. This will come up later when we want to make the C type for this function more convenient.
On the Racket side, it’s natural to represent the array of dashes as either a list or vector of numbers. Given that, a fairly literal translation of the C type above might look like the following:
|
This type includes a type constructor we haven’t seen yet: _list. This is a so-called custom function type that has special meaning inside of a _fun type. It lets you convert between a Racket list and a C array. Since arrays are often used for both input and output of a C function, the constructor requires you to specify the mode in which you are using the array.
Since we only want to provide a list from the Racket side to C, we’ll use the i input mode. We can then call the function like this:
(cairo-set-dash ctx |
(list 50.0 10.0 10.0 10.0) |
4 |
-50.0) |
Note that because of how we defined the type of cairo-set-dash we had to provide the length of the input list as a separate argument! This seems pretty silly since it’s very easy to get the length of a Racket list and because this is a likely source of mistakes. It would be preferable to compute the length argument automatically.
Luckily, the _fun type constructor actually lets you do this with the (name : type) syntax for naming arguments in combination with the (type = expr) syntax for supplying computed arguments:
|
When a computed argument is specified with a =, it’s not necessary to provide the argument on the Racket side. So cairo-set-dash is now an arity 3 function that can be called like this:
(cairo-set-dash ctx |
(list 50.0 10.0 10.0 10.0) |
-50.0) |
This means we’ll never make a mistake in passing the length argument to Cairo. Just as an aside, it’s also possible to use Racket vectors instead of lists by using the _vector type constructor.
Putting it all together, we can reproduce the dashes example like this:
(define dashes '(50.0 10.0 10.0 10.0)) | ||
(define offset -50.0) | ||
| ||
(cairo-set-dash ctx dashes offset) | ||
(cairo-set-line-width ctx 10.0) | ||
| ||
(cairo-move-to ctx 128.0 25.6) | ||
(cairo-line-to ctx 230.4 230.4) | ||
(cairo-rel-line-to ctx -102.4 0.0) | ||
| ||
| ||
(cairo-stroke ctx) | ||
| ||
(show bt) | ||
Result arguments and C structs
For some more advanced FFI hacking, let’s consider the problem of drawing some text into a predetermined space. In particular, we have our usual 256x256 bitmap that we want to draw some text into:
> (define txt-bt (make-bitmap 256 256)) |
> (define txt-surface (send txt-bt get-handle)) |
> (define txt-ctx (cairo-create txt-surface)) |
Our challenge is to make a Racket function that takes a string (let’s assume we can draw it in one line) and draws it into this bitmap. Since we are taking an arbitrary string, we will need to figure out how to scale the text to fit. To make it simple, let’s just scale the text to fit the width and assume the height will be okay.
To implement the key step of measuring the text size, we can use the cairo_text_extents function. Its type signature is as follows:
void |
cairo_text_extents (cairo_t *cr, |
const char *utf8, |
cairo_text_extents_t *extents); |
The interesting part of this signature is that cairo_text_extents_t is a struct type:
/* from the Cairo docs */ |
typedef struct { |
double x_bearing; |
double y_bearing; |
double width; |
double height; |
double x_advance; |
double y_advance; |
} cairo_text_extents_t; |
We haven’t yet seen how to handle C structs with the FFI, but it’s not too tricky. Support for C structs comes built-in and will look familiar if you’re used to Racket structs. We can directly translate the documented definition above into a define-cstruct declaration:
; the leading underscore is mandatory | |||||||
|
This declaration does a couple of things. First, it defines a bunch of handy C types related to the struct for us:
> _cairo_text_extents_t |
#<ctype> |
; pointer to struct |
> _cairo_text_extents_t-pointer |
#<ctype> |
; allows NULL pointer |
> _cairo_text_extents_t-pointer/null |
#<ctype> |
Along with functions that look like regular Racket struct operations:
; a struct constructor |
> make-cairo_text_extents_t |
#<procedure:make-cairo_text_extents_t> |
; a field selector |
> cairo_text_extents_t-width |
#<procedure:cairo_text_extents_t-width> |
; a predicate for the struct |
> cairo_text_extents_t? |
#<procedure:^TYPE?> |
; a field mutation function |
> set-cairo_text_extents_t-width! |
#<procedure:set-cairo_text_extents_t-width!> |
With the struct type defined, it’s easy to come up with a rudimentary interface for cairo-text-extents:
|
In order to actually use this function, we need to create a text extents struct and provide it as a pointer. Conveniently, the FFI treats instances of C structs as pointers so this is pretty straightforward:
| |||
| |||
> (cairo_text_extents_t-width extents) | |||
54.0 |
This style of programming feels awfully imperative though. Since we’re in a functional language, it would be nice to avoid the manual creation of the struct. We can define an alternate version of the cairo-text-extents FFI wrapper by combining named arguments, a new _ptr type constructor, and a neat feature of _fun that lets you customize the return result:
|
The _ptr constructor works like the _list constructor we saw earlier in this blog post but typically for a single object. Since we are passing in a value to use as an output, we specify the o mode to _ptr. In output mode, this type will automatically allocate a new instance of the type (using the malloc function) and arrange for it to be passed in as a pointer.
The strangest part of this example is that there are now two uses of the -> form! By providing a second arrow, we can customize what the FFI wrapper returns. The expression to the right of the second arrow is just Racket code that can reference previously named arguments. The result of evaluating this expression is used instead of the normal return result for calls to the wrapped function. In this case, we just return the struct that was allocated for us.
Using this new version of the wrapper is much simpler:
| ||
54.0 |
With that in hand, it’s pretty easy to write the function we set out to write:
(define-cairo cairo-show-text |
(_fun _cairo_t _string -> _void) |
#:c-id cairo_show_text) |
(define-cairo cairo-scale |
(_fun _cairo_t _double _double -> _void) |
#:c-id cairo_scale) |
; String -> Void |
; draws a string scaled horizontally |
(define (fit-text str) |
(define padding 20) |
(cairo-move-to txt-ctx (/ padding 2.0) 128.0) |
(define extents |
(cairo-text-extents* txt-ctx str)) |
(define x-bearing |
(cairo_text_extents_t-x-bearing |
extents)) |
(define width |
(cairo_text_extents_t-width |
extents)) |
(define scale (/ (- 256.0 padding) |
(+ x-bearing width))) |
(cairo-scale txt-ctx scale scale) |
(cairo-show-text txt-ctx str)) |
And to conclude part 2 of this tutorial, here’s an example use of the new fit-text function:
> (fit-text "Saluton, Mondo / Hallo, mundo") |
> (show txt-bt) |