1.3 Composite Foreign Data
Many C functions work with simple forms of data—
1.3.1 Array Arguments
Let’s look at the “dash” example from Cairo’s samples page:
double dashes[] = {50.0, 10.0, 10.0, 10.0}; |
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); |
Two of the new functions are straightforward to use:
(define-cairo cairo_rel_line_to (cairo_t* double_t double_t . -> . void_t))
(define-cairo cairo_curve_to (cairo_t* double_t double_t double_t double_t double_t double_t . -> . void_t))
The most interesting function is cairo_set_dash, which takes an array argument. The C type signature for cairo_set_dash is
void cairo_set_dash (cairo_t *cr, |
const double *dashes, |
int num_dashes, |
double offset); |
where the num_dashes argument tells cairo_set_dash the length of the dashes array.
With ffi2, we set up the array argument by allocating memory using ffi2-malloc and then filling the memory with a sequence of ffi2-set! assignments. The allocated memory is then passed to cairo_set_dash as a pointer. The following approach defines cairo_set_dash_raw as a direct reference to the C function that expects a pointer, and then it defines a Racket-friendly wrapper that converts a list into an allocated array.
; Low-level binding: takes a pointer and length (define-cairo cairo_set_dash_raw (cairo_t* void_t* int_t double_t . -> . void_t) #:c-id cairo_set_dash)
; Racket-friendly wrapper that takes a list (define (cairo-set-dash ctx dashes offset) (define n (length dashes)) (define arr (ffi2-malloc double_t n)) (for ([d (in-list dashes)] [i (in-naturals)]) (ffi2-set! arr double_t i d)) (cairo_set_dash_raw ctx arr n offset))
> (cairo-set-dash cr (list 50.0 10.0 10.0 10.0) -50.0)
This wrapper approach is fine, but the conversion from a Racket list to a C array is also something that can be handled by a new converting type. In fact, the ffi2 library provides a list_t type constructor to implement that conversion, where list_t takes the element type as an argument. At the same time, we need to pass the length of the list/array to cairo_set_dash, and that’s a separate argument. To deal with that kind of interaction among arguments, when you define a function type with ->, you can give names to arguments and refer to them in later expressions that supply automatic arguments.
(define-cairo cairo_set_dash (cairo_t* [lst : (list_t double_t)] [int_t = (length lst)] double_t . -> . void_t))
The lst : part of this definition gives a name to the value supplied as the second argument to cairo_set_dash. The (list_t double_t) part insists that the argument is a list of floating-point numbers, and it converts that list to an allocated array. For the third argument, meanwhile, the = (length lst) part computes the argument automatically (i.e., it’s not merely optional, but never provided by a caller). The end result is that cairo_set_dash behaves the same as the wrapper cairo-set-dash function.
(define-values (bt cr) (make)) (define dashes '(50.0 10.0 10.0 10.0)) (define offset -50.0) (cairo-set-dash cr dashes 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)
> (show bt)
1.3.2 C Structs
For a more advanced example, let’s measure text to scale it into our bitmap. The relevant Cairo function is cairo_text_extents:
void cairo_text_extents (cairo_t *cr, |
const char *utf8, |
cairo_text_extents_t *extents); |
where cairo_text_extents_t is a struct:
typedef struct { |
double x_bearing; |
double y_bearing; |
double width; |
double height; |
double x_advance; |
double y_advance; |
} cairo_text_extents_t; |
We can define a cairo_text_extents_t type for Racket using define-ffi2-type with the struct_t type constructor:
(define-ffi2-type cairo_text_extents_t (struct_t [x_bearing double_t] [y_bearing double_t] [width double_t] [height double_t] [x_advance double_t] [y_advance double_t]))
This single declaration automatically creates several bindings:
cairo_text_extents_t: works both as a type and as a constructor of a cairo_text_extents_t instance.
cairo_text_extents_t*: a type for a pointer to a cairo_text_extents_t instance.
cairo_text_extents_t*?: a predicate for pointers to cairo_text_extents_t instances.
cairo_text_extents_t-width, cairo_text_extents_t-x_bearing, etc.: field accessors, which take a pointer to a cairo_text_extents_t instance and extract a corresponding field value, converting it from C to Racket as needed.
set-cairo_text_extents_t-width!, etc.: field mutators.
Using this definition, you can construct instances directly:
(define extents (cairo_text_extents_t 0.0 0.0 0.0 0.0 0.0 0.0))
Or allocate one with ffi2-malloc:
(define extents (ffi2-malloc cairo_text_extents_t))
We can define a “raw” version of cairo_text_extents that will write into a struct pointer that the caller provides:
(define-cairo cairo_text_extents_raw (cairo_t* string_t cairo_text_extents_t* . -> . void_t) #:c-id cairo_text_extents)
A string_t type for the second argument lets a caller provide a Racket string that is converted into a UTF-8-encoded, null-terminated representation of the string for the C side.
Note that the third argument to cairo_text_extents_raw is cairo_text_extents_t* (ending with *), not cairo_text_extents_t (no *). The C function accepts a pointer argument and not an immediate struct argument. On the Racket size, cairo_text_extents_t* and cairo_text_extents_t have the same representation, but the distinction is crucial on the C side, because functions accept and return struct values differently than pointers.
To use cairo_text_extents_raw, a caller must allocate their own cairo_text_extents_t instance:
> (define extents (ffi2-malloc cairo_text_extents_t)) > (cairo_text_extents_raw cr "hello world" extents) > (cairo_text_extents_t-width extents) 54.0
Similar to the way we made cairo_set_dash allocate an array, we can improve cairo_text_extents side by having it automatically allocate a cairo_text_extents_t instance. But in addition to passing that allocated memory on to the C function, we need to return it to a caller in Racket. The #:result option for -> provides a result expression to substitute for the C function’s result.
(define-cairo cairo_text_extents (cairo_t* string_t [exts : cairo_text_extents_t* = (ffi2-malloc cairo_text_extents_t)] . -> . void_t #:result exts))
> (cairo_text_extents_t-width (cairo_text_extents cr "hello world")) 54.0
To round out the example, let’s implement a function that draws text scaled to fit the bitmap width:
(define-cairo cairo_show_text (cairo_t* string_t . -> . void_t)) (define-cairo cairo_scale (cairo_t* double_t double_t . -> . void_t))
(define (fit-text cr str) (define padding 20) (cairo_move_to cr (/ padding 2.0) 128.0) (define extents (cairo_text_extents cr 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 cr scale scale) (cairo_show_text cr str))
> (define-values (txt-bt txt-cr) (make)) > (fit-text txt-cr "Saluton, Mondo / Hallo, mundo") > (show txt-bt)
1.3.3 Callbacks from C to Racket
To save a Cairo-based drawing to a PNG file, we could use the save-file method of the bitmap that we’re writing into. Let’s instead directly use Cairo’s cairo_surface_write_to_png_stream to write bytes to a file. The C signature is
cairo_status_t |
cairo_surface_write_to_png_stream (cairo_surface_t *surface, |
cairo_write_func_t write_func, |
void *closure_data); |
where cairo_write_func_t is a function type:
cairo_status_t |
(*cairo_write_func_t) (void *closure_data, |
const unsigned char *data, |
unsigned int length); |
The closure_data pointer as the third argument to cairo_surface_write_to_png_stream is passed along as the first argument to write_func, which is C’s manual way of forming closures. We’ll let Racket take care of closures automatically for us, so we’ll just use a NULL pointer as closure_data. The interesting part is the function pointer passed as write_func.
For the type of the function argument to cairo_surface_write_to_png_stream, we can nest a -> type for the argument inside a -> type for the function. We’ll also handle the result status by raising an exception if the status is not 0.
(define-cairo cairo_surface_write_to_png_stream (cairo_surface_t* (ptr_t ptr_t int_t . -> . int_t) ptr_t . -> . (r : int_t) #:result (unless (zero? r) (error "write failed"))))
Then, when we pass a Racket function as the second argument to cairo_surface_write_to_png_stream, it will be called to write out bytes of the PNG encoding:
(define png-out (open-output-file "/tmp/img.png" #:exists 'truncate))
(cairo_surface_write_to_png_stream (ffi2-cast bt-surface #:to cairo_surface_t*) (lambda (ignored data len) (define png-data (make-bytes len)) (ffi2-memcpy (ffi2-cast png-data #:from bytes_ptr_t) data len) (write-bytes png-data png-out) ; return 0 to mean "success" 0) (uintptr_t->ptr_t 0)) (close-output-port png-out)
This example illustrates a technique for converting a pointer to bytes into a byte string. The write-bytes function wants a byte string, not a C-level pointer to bytes, so we copy from the data pointer into a freshly allocated byte string. The copy operation needs two pointers, so we cast a byte string to a pointer using bytes_ptr_t. Note that casting from bytes_t, instead of bytes_ptr_t, would make a copy of the byte string, which would not work.
One catch here is that a callback from a foreign function is always in atomic mode. In this case, the callback writes to a port png-out that is not used from any other thread, so atomic mode causes no problems; writing will not get stuck trying to acquire a lock.
Another subtlety is that the callback must remain live as long as it might be called. The Racket procedure supplied as a callback will not be garbage collected while it is being called, of course, but the C representation of the callback is a wrapper that refers to the Racket procedure, not the other way around. So, the callback could potentially trigger a garbage collector that reclaims the callback wrapper. In this case, the callback is kept live by virtue of being an argument to the foreign function, and the callback is not used after cairo_surface_write_to_png_stream returns. When a callback is registered for future use, then more care must be taken to retain the pointer representing a callback.

