On this page:
1.3.1 Array Arguments
1.3.2 C Structs
1.3.3 Callbacks from C to Rhombus

1.3 Composite Foreign Data🔗ℹ

Many C functions work with simple forms of data—such as integers, floating-point numbers, and opaque pointers—but things get more interesting with arrays, C structures, and callback functions.

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 define:

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

cairo.fun 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 ffi, we set up the array argument by allocating memory using new and then filling the memory with 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 Rhombus-friendly wrapper that converts a list into an allocated array.

// Low-level binding: takes a pointer and length

cairo.fun cairo_set_dash_raw(cairo_t*, ptr_t, int_t, double_t) :: void_t:

  ~c_id: cairo_set_dash

// Rhombus-friendly wrapper that takes a list

fun cairo_set_dash(ctx, dashes :: List.of(Flonum), offset :: Flonum):

  def n = List.length(dashes)

  def arr = new ~manual double_t[n]

  for (i in 0 .. n):

    arr[i] := dashes[i]

  cairo_set_dash_raw(ctx, cast (ptr_t) arr, n, offset)

  free(arr)

> cairo_set_dash(cr, [50.0, 10.0, 10.0, 10.0], -50.0)

This wrapper approach is fine, but the conversion from a Rhombus list to a C array is also something that can be handled by a new converting type. In fact, the ffi 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.

cairo.fun cairo_set_dash(

  cairo_t*,

  lst :: list_t(double_t),

  int_t = List.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 = List.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 defined above.

def (bt, cr) = make()

def dashes = [50.0, 10.0, 10.0, 10.0]

def 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)

image

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 Rhombus using foreign.struct:

foreign.struct cairo_text_extents_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 type for use with new to allocate 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: an annotation for pointers to cairo_text_extents_t instances.

  • cairo_text_extents_t as a veneer: field accessors like .width and .x_bearing that take a pointer to a cairo_text_extents_t instance and extract a corresponding field value, converting it from C to Rhombus as needed.

Using this definition, you can construct instances directly:

def extents = new cairo_text_extents_t(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)

Or allocate one uninitialized:

def extents = new 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:

cairo.fun 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 Rhombus 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 Rhombus side, 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:

> def extents = new cairo_text_extents_t

> cairo_text_extents_raw(cr, "hello world", extents)

> extents.width

54.0

Similar to the way we made cairo_set_dash allocate an array, we can improve cairo_text_extents by having it automatically allocate a cairo_text_extents_t instance. A body after the result type in a foreign.fun definition provides a result expression to substitute for the C function’s result. An extra :: foreign.type cairo_text_extents_t* provides a Rhombus result annotation, as opposed to the C function’s result.

The result could also be written as foreign.type followed by cairo_text_extents_t* instead of just cairo_text_extents_t. The Racket representation is the same for cairo_text_extents_t and cairo_text_extents_t*, but using the * operator for a type would mean that we must use foreign.type to bring the Rhombus annotation and C type contexts, while cairo_text_extents_t is defined for both contexts.

cairo.fun cairo_text_extents(

  cairo_t*,

  string_t,

  exts :: cairo_text_extents_t* = new cairo_text_extents_t

) :: void_t :: cairo_text_extents_t:

  exts

> cairo_text_extents(cr, "hello world").width

54.0

To round out the example, let’s implement a function that draws text scaled to fit the bitmap width:

cairo.fun cairo_show_text(cairo_t*, string_t) :: void_t

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

fun fit_text(cr, str):

  def padding = 20

  cairo_move_to(cr, padding / 2.0, 128.0)

  def extents = cairo_text_extents(cr, str)

  def x_bearing = extents.x_bearing

  def width = extents.width

  def scale = (256.0 - padding) / (x_bearing + width)

  cairo_scale(cr, scale, scale)

  cairo_show_text(cr, str)

> def (txt_bt, txt_cr) = make()

> fit_text(txt_cr, "Saluton, Mondo / Hallo, mundo")

> show(txt_bt)

image

1.3.3 Callbacks from C to Rhombus🔗ℹ

To save a Cairo-based drawing to a PNG file, we could use Rhombus’s drawing utilities. 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 Rhombus 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.

cairo.fun cairo_surface_write_to_png_stream(

  cairo_surface_t*,

  (ptr_t, ptr_t, int_t) -> int_t,

  ptr_t

) :: (r :: int_t) :: Void:

  unless r == 0 | error("write failed")

Then, when we pass a Rhombus function as the second argument to cairo_surface_write_to_png_stream, it will be called to write out bytes of the PNG encoding:

def png_out = Port.Output.open_file("/tmp/img.png", ~exists: #'truncate)

cairo_surface_write_to_png_stream(

  cast (cairo_surface_t*) bt_surface,

  fun (ignored, data, len):

    def png_data = Bytes.make(len)

    memcpy(cast ~from (bytes_ptr_t) ~to (ptr_t) png_data, data, len)

    Port.Output.write_bytes(png_out, png_data)

    // return 0 to mean "success"

    0,

  uintptr_to_ptr(0)

)

This example illustrates a technique for converting a pointer to bytes into a byte string. The Port.Output.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 Rhombus 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 Rhombus 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.