On this page:
1.2.1 Loading C Libraries
1.2.2 Finding C Functions
1.2.3 Increasing Safety with Tagged Pointers
1.2.4 Reducing Boilerplate for Function Definitions
1.2.5 Defining a Type Conversion
1.2.6 Putting it All Together

1.2 Foreign-Function Basics🔗ℹ

To use the FFI, you must have in mind

  • A particular shared library with functions you want to call: A shared library corresponds to a file with a suffix such as ".dll", ".so", or ".dylib" (depending on the platform), or it might be a library within a ".framework" directory on Mac OS.

  • A particular set of names exported by the library: Shared libraries can export names for any kind of C value, but they mostly export functions. The exported names generally match the names as used from C.

  • The C-level type of the exported name: This is typically a function type that involves a sequence of argument types and a result type.

As a running example, let’s try calling functions exported by the Cairo graphics library. Specifically, we’ll start out by imitating 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);

  cairo_stroke (cr);

The cr variable here is a Cairo drawing context, which renders commands to a drawing surface. As it happens, Rhombus bitmaps are implemented using Cairo, and we can get a drawing surface that targets a bitmap using the draw.Bitmap.handle property to get a Racket object, and then call that object’s #{get-handle} method. The method returns a Cairo surface pointer representation that is compatible with ffi/unsafe), so we must convert for ffi it using cpointer_to_ptr.

import:

  draw

  rhombus/rkt_obj

def bt = draw.Bitmap([256, 256])

def bt_surface = cpointer_to_ptr(rkt_obj.send bt.handle.#{get-handle}())

1.2.1 Loading C Libraries🔗ℹ

The Lib.load function gets a handle to a library by searching the filesystem. The search is based on the library file’s base name (without a platform-specific suffix for shared libraries) and a list of versions to try (where #false tries omitting the version):

import:

  ffi open

def cairo_lib = Lib.load("libcairo", ["2", #false])

Knowing the library’s name and/or path is often the trickiest part of using the FFI. Sometimes, when using a library name without a path prefix or file suffix, the library file can be located automatically, especially on Unix.

In the case of Cairo, the problem is simplified by the fact that the library is included with a Racket distribution for Windows or Mac OS. Using the base file name "libcairo" with version "2" is likely to find the library as bundled with Racket or as supplied by the operating system—usually something like "/usr/lib/libcairo.2.so" on a Unix installation.

On Windows, if you are not running in DrRacket, then "libcairo-2.dll" will be found, but loading it may fail because its dependencies cannot be found; the DLLs bundled with Racket are not in the operating system’s search path. The easy solution is to import lib("racket/draw/unsafe/cairo-lib.rkt"), which will load all dependencies.

1.2.2 Finding C Functions🔗ℹ

Assuming that cairo_lib is successfully defined for the loaded Cairo shared library, we can use it to extract the addresses of functions from the library. Let’s start with cairo_create, which accepts a surface (like the one we have from a Rhombus bitmap) to create a drawing context.

  cairo_t * cairo_create (cairo_surface_t *target);

We can get a pointer to this function using Lib.find:

> cairo_lib!!.find("cairo_create")

#<ptr_t>

To call this function, we will need to give that pointer a type, casting via cast to convert it to a Rhombus function. We could give the function the simplest possible type, which is a function type using -> that expects a generic pointer argument and returns a generic pointer result:

def cairo_create:

  cast (ptr_t -> ptr_t) cairo_lib!!.find("cairo_create")

At this point, calling cairo_create on bt_surface would work:

> cairo_create(bt_surface)

#<ptr_t>

1.2.3 Increasing Safety with Tagged Pointers🔗ℹ

Using a generic pointer type is especially dangerous, because there will be many different pointer types that are used with Cairo functions. It’s better to give each pointer type a specific name. Named pointer types not only improve readability, they enable checks that help prevent using the wrong kind of pointer as a function argument.

foreign.type cairo_t

foreign.type cairo_surface_t

The type cairo_surface_t is defined here as an opaque type, meaning that its C representation is unspecified, but the derived type cairo_surface_t* is a pointer type tagged with cairo_surface_t*. A value with type cairo_surface_t* is accepted wherever ptr_t is expected, but a ptr_t value is not accepted where cairo_surface_t* is expected. A foreign.type form for cairo_surface_t also defines cairo_surface_t as an annotation to recognize Rhombus pointer representations that are specifically tagged as cairo_surface_t* pointers. A function that expects a cairo_surface_t* pointer uses that annotation to check arguments.

def cairo_create:

  cast (cairo_surface_t* -> cairo_t*) cairo_lib!!.find("cairo_create")

In this case, calling cairo_create with bt_surface would fail, because bt_surface has type ptr_t, not cairo_surface_t*. We are sure that bt_surface really is a surface pointer, so we can cast it using cast:

> bt_surface is_a cairo_surface_t

#false

> cast (cairo_surface_t*) bt_surface

#<cairo_surface_t*>

> cast (cairo_surface_t*) bt_surface is_a cairo_surface_t

#true

> cairo_create(cast (cairo_surface_t*) bt_surface)

#<cairo_t*>

1.2.4 Reducing Boilerplate for Function Definitions🔗ℹ

Instead of finding a function pointer and then separately casting it to the right function type, combine those two steps using foreign.fun:

foreign.fun cairo_create(cairo_surface_t*) :: cairo_t*:

  ~lib: cairo_lib

In this definition, cairo_create is used both as the name to define and the name to locate in the C library. As we will see in later examples, an optional ~c_id clause for foreign.fun can separately specify the name to find in the C library, if needed.

Since we will define many functions from cairo_lib, it’s even better to use foreign.linker to define a new form that’s like foreign.fun, except that it has the ~lib part built in:

foreign.linker cairo:

  ~lib: cairo_lib

cairo.fun cairo_create(cairo_surface_t*) :: cairo_t*

cairo.fun cairo_move_to(cairo_t*, double_t, double_t) :: void_t

cairo.fun cairo_line_to(cairo_t*, double_t, double_t) :: void_t

cairo.fun cairo_set_line_width(cairo_t*, double_t) :: void_t

cairo.fun cairo_stroke(cairo_t*) :: void_t

You may notice that the C type names double and void are represented here by names that end in _t: double_t and void_t. The ffi convention is to use a _t suffix for all type and type-constructor names, even for traditional C types like int, double, and void.

At this point, we have enough functions to draw a line on the bitmap.

import:

  pict

fun show(bt):

  pict.rectangle(~around: pict.bitmap(bt), ~line_width: 2)

> def cr = cairo_create(cast (cairo_surface_t*) bt_surface)

> cairo_move_to(cr, 50.0, 50.0)

> cairo_line_to(cr, 206.0, 206.0)

> cairo_set_line_width(cr, 5.0)

> cairo_stroke(cr)

> show(bt)

image

1.2.5 Defining a Type Conversion🔗ℹ

To match the original Cairo example, we’ll need to call cairo_set_line_cap. The cairo_set_line_cap function takes a cairo_line_cap_t argument, where cairo_line_cap_t is defined in C using enum. The most natural translation to Rhombus is to use a symbol for each variant, instead of integer constants. We can define a new type cairo_line_cap_t using foreign.type:

def line_cap_symbols = [#'butt, #'round, #'square]

foreign.type cairo_line_cap_t:

  ~extends int_t

  ~predicate: fun (v): v in line_cap_symbols

  ~rhombus_to_c: fun (sym): line_cap_symbols.index(sym)

  ~c_to_rhombus: fun (i): line_cap_symbols[i]

cairo.fun cairo_set_line_cap(cairo_t*, cairo_line_cap_t) :: void_t

cairo.fun cairo_get_line_cap(cairo_t*) :: cairo_line_cap_t

> cairo_set_line_cap(cr, #'round)

> cairo_get_line_cap(cr)

#'round

Mapping between symbols and integers is common enough that ffi provides foreign.enum, which achieves the same result as using foreign.type with explicit ~predicate, ~rhombus_to_c, and ~c_to_rhombus options.

foreign.enum cairo_line_cap_t int_t

| butt

| round

| square

1.2.6 Putting it All Together🔗ℹ

We can now implement the original Cairo example. Here’s the complete code and the result that DrRacket shows:

import:

  ffi open

  draw

  pict

  rhombus/rkt_obj

def cairo_lib = Lib.load("libcairo", ["2", #false])

foreign.linker cairo:

  ~lib: cairo_lib

foreign.type cairo_t

foreign.type cairo_surface_t

foreign.enum cairo_line_cap_t int_t

| butt

| round

| square

cairo.fun cairo_create(cairo_surface_t*) :: cairo_t*

cairo.fun cairo_move_to(cairo_t*, double_t, double_t) :: void_t

cairo.fun cairo_line_to(cairo_t*, double_t, double_t) :: void_t

cairo.fun cairo_set_line_width(cairo_t*, double_t) :: void_t

cairo.fun cairo_stroke(cairo_t*) :: void_t

cairo.fun cairo_set_line_cap(cairo_t*, cairo_line_cap_t) :: void_t

fun make():

  def bt = draw.Bitmap([256, 256])

  def bt_surface = cpointer_to_ptr(rkt_obj.send bt.handle.#{get-handle}())

  values(bt, cairo_create(cast (cairo_surface_t*) bt_surface))

fun show(bt):

  pict.rectangle(~around: pict.bitmap(bt), ~line_width: 2)

def (bt, cr) = make()

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, #'round)

cairo_stroke(cr)

show(bt)

image