Mixing FFI, Fiddle, and C Extension in Ruby
Tags: ruby, ffi
Published: 2023-10-26 12:14:57

## Introduction

If you're working with Ruby and need to invoke a function written in C language, there are some convenient gems available: [Ruby-FFI](https://github.com/ffi/ffi) and [Fiddle](https://github.com/ruby/fiddle).

Ruby-FFI has many features, handling most challenges you might encounter. Fiddle might seem a bit less convenient, but being an official Ruby gem, it is available from the start in most environments.

## When You Want to Use Both FFI or Fiddle and C Extension

There can be situations where you want to rewrite certain parts of your Gem implemented with FFI or Fiddle into C extensions. Function calls using libffi are known to be nearly 100 times slower than that of native C extensions. If large numbers of calls need to be made with a demand for speed, you might want to consider rewriting the FFI-implemented function using C extensions.

## The Method

### Basic Principles

The main challenge here is determining how to handle the pointer of FFI or Fiddle's structure as an argument in a C extension function. The solution is straightforward: get the memory address from the `Fiddle::Pointer` or `FFI::Pointer` Ruby objects.

Here you will learn how to write a [C Extension function](https://github.com/rcairo/rcairo/blob/85bb3401ee7f8d979db51107cae529185c07608d/ext/cairo/rb_cairo_context.c#L175-L218) that takes FFI::Pointer as an argument, referring to the [rcairo](https://github.com/rcairo/rcairo) gem.

### Check for the existence of the constant FFI::Pointer

We begin by ensuring the constant `FFI::Pointer` is defined. This step verifies that `require "ffi"` has been executed and `Fiddle::Pointer` is available.

```c
  if (NIL_P (rb_cairo__cFFIPointer))
    {
      rb_raise (rb_eNotImpError,
                "%s: FFI::Pointer is required",
                rb_id2name (rb_frame_this_func ()));
    }
```

The `rb_cairo__cFFIPointer` is pre-set in [Init_cairo_private](https://github.com/rcairo/rcairo/blob/85bb3401ee7f8d979db51107cae529185c07608d/ext/cairo/rb_cairo_private.c#L235C1-L254).

```c
void
Init_cairo_private (void)
{
  // -- code omission --
  if (rb_const_defined (rb_cObject, rb_intern ("FFI")))
    {
      rb_cairo__cFFIPointer =
        rb_const_get (rb_const_get (rb_cObject, rb_intern ("FFI")),
                      rb_intern ("Pointer"));
    }
  else
    {
      rb_cairo__cFFIPointer = Qnil;
    }
}
```

In the case of Fiddle, execute:

```c
rb_const_get (rb_const_get (rb_cObject, rb_intern ("Fiddle")), rb_intern ("Pointer"));
```

### Ensure Argument Type Consistency

After confirming the constant, we ensure the argument classes are consistent. As `FFI::Pointer` and `Fiddle::Pointer` obtain addresses using relatively common names - `address` and `to_i` respectively, performing a type check helps prevent errors.

```c
  if (!RTEST (rb_obj_is_kind_of (pointer, rb_cairo__cFFIPointer)))
    {
      rb_raise (rb_eArgError,
                "must be FFI::Pointer: %s",
                rb_cairo__inspect (pointer));
    }
```

### Acquiring the Address

With FFI, you can get the address with the `address` method.

```ruby
# Ruby-FFI
pt = FFI::MemoryPointer.new(:int)
p pt.address
```

With Fiddle, `to_i` method helps in getting the address.

```ruby
# Fiddle
pt = Fiddle::Pointer.new(Fiddle::SIZEOF_INT)
p pt.to_i
```

In C extensions, these Ruby methods are invoked using `rb_funcall`.

```c
rb_funcall (ffi_pointer, rb_intern ("address"), 0)
rb_funcall (fiddle_pointer, rb_intern ("to_i"), 0)
```

### Call a C Function Using the Acquired Address as an Argument

The above Ruby code is executed within the C extension code.

```c
VALUE rb_cr_address;
rb_cr_address = rb_funcall (pointer, rb_intern ("address"), 0);
cr = NUM2PTR (rb_cr_address);
cr_check_status (cr);
```

Here, the `NUM2PTR` macro is not provided by `ruby.h` so you'll need to define it yourself:

```c
#if SIZEOF_LONG == SIZEOF_VOIDP
#  define PTR2NUM(x)   (ULONG2NUM((unsigned long)(x)))
#  define NUM2PTR(x)   ((void *)(NUM2ULONG(x)))
#else
#  define PTR2NUM(x)   (ULL2NUM((unsigned long long)(x)))
#  define NUM2PTR(x)   ((void *)(NUM2ULL(x)))
#endif
```

The `cr_check_status` function calls the native Cairo function `cairo_status_to_string`. It's safe to insert a function like this in the middle.

### Creating a Ruby Object

To create a Ruby object from the obtained address, do as follows:

```c
rb_cr = rb_obj_alloc (self);
cairo_reference (cr);
RTYPEDDATA_DATA (rb_cr) = cr;
rb_ivar_set (rb_cr, cr_id_surface, Qnil);
```

Use `rb_obj_alloc` to create an instance of the class (self, in this case). `cairo_reference()` is a Cairo function that increases the reference count, which ensures Garbage Collection won't remove your `ruby-FFI` object. `RTYPEDDATA_DATA` is used to access the data of TypedData Objects directly. Lastly, `rb_ivar_set` sets an instance variable.

## Basic Example

Let's walk through a basic example. For this, `piyo.h` and `piyo.c` have been prepared as targets to create bindings.

piyo.h

```c
#ifndef PIYO_H
#define PIYO_H

#include <stdio.h>

typedef struct Piyo
{
    int age;
    char *name;
} Piyo;

void displayPiyoInfo(const Piyo *piyo);

#endif
```

piyo.c

```c
#include "piyo.h"

void displayPiyoInfo(const Piyo *piyo)
{
  printf("Name: %s\n", piyo->name);
  printf("Age: %d\n", piyo->age);
}
```

Write the C extension so that the following code functions correctly:

```ruby
require 'fiddle/import'
require_relative './piyo.so'

module Piyo
  Piyo = Fiddle::Importer.struct(['int age', 'char* name'])
end

tori_name = 'piyoko'

Piyo::Piyo.malloc(Fiddle::RUBY_FREE) do |piyo|
  piyo.age = 100
  piyo.name = tori_name
  Piyo.display_info(piyo)
end
```

Ruby C Extension

piyo_rb.c

```c
#include "ruby.h"
#include "piyo.h"

#if SIZEOF_LONG == SIZEOF_VOIDP
#define PTR2NUM(x) (ULONG2NUM((unsigned long)(x)))
#define NUM2PTR(x) ((void *)(NUM2ULONG(x)))
#else
#define PTR2NUM(x) (ULL2NUM((unsigned long long)(x)))
#define NUM2PTR(x) ((void *)(NUM2ULL(x)))
#endif

VALUE rb_cFiddlePointer;

VALUE rb_display_info(VALUE self, VALUE piyo)
{
  Piyo *ptr;

  VALUE rb_address = rb_funcall(piyo, rb_intern("to_i"), 0);
  ptr = NUM2PTR(rb_address);

  displayPiyoInfo(ptr);

  return Qnil;
}

void Init_piyo(void)
{
  VALUE mPiyo = rb_define_module("Piyo");
  rb_define_singleton_method(mPiyo, "display_info", rb_display_info, 1);
}
```

Create Makefile with:

extconf.rb

```ruby
require 'mkmf'

find_header('piyo.h', __dir__)
create_makefile('piyo')
```

Compile with:

```
ruby extconf.rb
make
```

Execute with:

```
ruby test.rb
```

If everything runs correctly, you should see the output as:

```
Name: piyoko
Age: 100
```

While this is a simple example and doesn't include every aspect, such as class definition verification and argument type checks, you will need to add these elements to transition it into a practical gem.

That's all for this post.

---

This article was translated from Japanese to English by a collaboration of ChatGPT, DeepL, and the author.  The author, despite having the weakest command of English among the three, played a crucial role in providing instructions to ChatGPT and DeepL. In Japanese, 'Piyo' represents the chirping sound of a chick and is often used as a meta-syntax variable, following 'hoge' and 'fuga'.