The Racket Foreign-Function Interface

Introduction - Mechanisms for Bridging C and Scheme

In Glimmer projects, we often want to bridge make functions written in C available to Scheme (usually one of the PLT Scheme variants). For example, in the original MediaScheme, we wanted to use Gimp functions, which are written in C. Similarly, in the new MediaScheme architecture, we want to call D-Bus functions that we've written in C.

The PLT folk provide two mechanisms for bridging C and Scheme - the Inside Racket C API (Flatt n.d.) and the Racket Foreign-Function Interface (Barzilay, n.d.). The Inside API gives you access to low level details of the language - the C structs that represent Scheme objects, ways of building lambdas and closures, conversion routines between internal representation of values and the normal C representations of values. The FFI, in contrast, lets the C programmer ignore most of the internal details, and just focus on incorporating C procedures into Scheme. In effect, the FFI provides most of the wrapper code for our C procedures - converting between the different types, type checking, etc.

Given that the FFI is so much easier, why do we need the Inside API?

But much of the time, the FFI is easier to use, so let's learn it.

Overview: The Necessary Steps

So, what do you need to do in order to get a C function into Racket using the FFI?

That's not so bad, is it?

Our First Example: Squaring an Integer

Choosing a Function to Bridge

In our first example, we're going to bridge a simple function that squares an integer. (Yes, it's clear that we can just as easily write this in Scheme. We're looking at this example primarily to help us learn the basics.) So, what does the function look like?

/**
 * Square an integer.
 */
int
isquare (int i)
{
  return i * i;
} // isquare

Identifying the Type of the Function

So, what's the type of the function? It's a function from one integer to one integer.

In C, we might say that it has type FUNCTION_INT__INT, with the following typedef for that type.

typedef int (*FUNCTION__INT__INT)(int);

In Racket, we will represent that type as

(_fun _int -> _int)

Yes, that's right, we use underscores to represent C types and we use an arrow to separate the input type and the output type.

Building Shared Object Code

We need to put that function in a C file. You know how to do this, but for convenience, I've put it in mylib1.c.

As you may recall, shared object code has the suffix .so, so we want to build mylib1.o from mylib1.c.

How to we build mylib1.so? We pass the -shared flag to the C compiler (at least to the gcc compiler). Unfortunately, instead of building the .so file we want, by default, the C compiler builds the legendary a.out. Hence, we want to write something like

cc --shared -o mylib1.so mylib1.c

See the section below on writing a Makefile for the normal updates to your Makefile to achieve this goal.

Loading the Racket FFI Libraries

If we're going to use the Racket FFI libraries, we need to load them into our program. We do so with the Racket require function. We will need ffi/unsafe and ffi/unsafe/define. (Yeah, C code is unsafe, so the FFI chooses names to ensure we remember that.)


; We need the FFI libraries.
(require ffi/unsafe
         ffi/unsafe/define)



Pretty simple, isn't it?

Loading Your Shared Object Code

The Racket FFI chooses an interesting way for you to load the shared object code. First, we turn the code into a definer. The definer is effectively a function that lets you define new functions by making a link to your C code.


; Set up mylib-define to be a link to our sample library
(define-ffi-definer mylib-define (ffi-lib "mylib1"))



Or, more generally

(define-ffi-definer name-of-definer (ffi-lib "prefix-of-so"))

Bridging the Function

We're almost done. All we need to do is tell the Scheme program about the particular function in your shared object code.


; An interface to 
;   int isquare (int i)
(mylib-define isquare (_fun _int -> _int))



That is, we call mylib-define using the name of the function in our library and the type signature of the function.

Experimenting With Our Code

At this point, the function should be available to our Scheme environment.

> (isquare 5)
25
> (isquare 2.3)
. . Scheme->C: expects argument of type <int32>, given 2.3
> (isquare 2 3)
. . ffi:proc: expects 1 argument, given 2: 2 3

Yay! Not only does it work, it checks the parameters for validity.

Detour: Using A Makefile

I"ll note that I hate remembering the particular command to build a shared object, even though the command isn't that bad. Hence, I tend to include a line in my Makefile to build the .so file. In GNU Make, one need only write

%.so: %.c
        $(CC) $(CFLAGS) -shared -o $@ $<

That is,

Forthcoming

References

Barzilay, Eli (n.d.). The Racket Foreign Interface, Version 5.2.1. Online document at http://docs.racket-lang.org/foreign/index.html. Accessed 29 June 2012.

Flatt, Matthew (n.d.). Inside: Racket C API, Version 5.2.1. Online document at http://docs.racket-lang.org/inside/index.html. Accessed 29 June 2012.

This page was generated by Siteweaver on Fri Jun 29 09:55:36 2012.
The source to the page was last modified on Fri Jun 29 09:55:35 2012.
This page may be found at http://www.cs.grinnell.edu/~rebelsky/Glimmer/Summer2012/RacketFFI/tutorial.html.