Use any C library from Ruby via Fiddle - the Ruby standard library's best kept secret.

 

Fiddle is a little-known module that was added to Ruby's standard library in 1.9.x. It allow you to interact directly with C libraries from Ruby. It even lets you inspect and alter the ruby interpreter as it runs.

It works by wrapping libffi, a popular C library that allows code written in one language to call methods written in another. In case you haven't heard of it, "ffi" stands for "foreign function interface." And you're not just limited to C. Once you learn Fiddle, you can use libraries written in Rust and other languages that support it.

Let's take a look at Fiddle. We'll start out with a simple example and then end by accessing an Arduino over a serial port. You don't need to know that much C. I promise. :)

A simple example

In plain-old Ruby, you always have to define a method before you can call it. It's the same with Fiddle. You can't just call a C function directly. Instead you have to create a wrapper for the C function, then call the wrapper.

In the example below, we're wrapping C's logarithm function. We're basically duplicating Ruby's Math.log.

require 'fiddle'

# We're going to "open" a library, so we have to tell Fiddle where
# it's located on disk. This example works on OSX Yosemite.
libm = Fiddle.dlopen('/usr/lib/libSystem.dylib')


# Create a wrapper for the c function "log". 
log = Fiddle::Function.new(
  libm['log'],            # Get the function from the math library we opened
  [Fiddle::TYPE_DOUBLE],  # It has one argument, a double, which is similar to ruby's Float
  Fiddle::TYPE_DOUBLE     # It returns a double
)

# call the c function via our wrapper
puts log.call(3.14159)

Making it pretty

The previous example works, but it's kind of verbose. I'm sure you can imagine how cluttered things would get if you had to wrap 100 functions. That's why Fiddle provides a nice DSL. It's exposed via the Fiddle::Importer mixin.

Using this mixin makes it short work to create a module full of external functions. In the example below, we're creating a module that contains several logarithm methods.

require 'fiddle'
require 'fiddle/import'

module Logs
  extend Fiddle::Importer
  dlload '/usr/lib/libSystem.dylib'
  extern 'double log(double)'
  extern 'double log10(double)'
  extern 'double log2(double)'
end

# We can call the external functions as if they were ruby methods!
puts Logs.log(10)   # 2.302585092994046
puts Logs.log10(10) # 1.0
puts Logs.log2(10)  # 3.321928094887362

Controling the serial port

Ok, so you finally bought one of those Arduinos you've been thinking about buying for years. You've got sending the text "hello world" to your computer once per second using the serial port.

Now, it'd be really nice if you could read that data using Ruby. And you can actually read from the serial port using Ruby's standard IO methods. But these IO methods don't let us configure the serial input with the level of granularity that we need if we're to be hardcore hardware hackers.

Fortunately, the C standard library provides all the functions we need to work with the serial port. And using Fiddle, we can access these C functions in Ruby.

Of course, there are already gems that do this for you. When I was working on this code I took a little inspiration from the rubyserial gem. But our goal is to learn how to do this stuff ourselves.

Examining  termios.h

In C, any filename ending in .h is a header file. It contains a list of all functions and constants that the library makes available to third party code (our code). The first step in wrapping any C library is going to be to find this file, open it and look around.

The library we're using is called termios. On OSX Yosemite, the header file is located at /usr/include/sys/termios.h. It will be somewhere else on linux.

Here's what termios.h looks like. I've condensed it quite a bit for clarity.

typedef unsigned long   tcflag_t;
typedef unsigned char   cc_t;
typedef unsigned long   speed_t;

struct termios {
    tcflag_t    c_iflag;    /* input flags */
    tcflag_t    c_oflag;    /* output flags */
    tcflag_t    c_cflag;    /* control flags */
    tcflag_t    c_lflag;    /* local flags */
    cc_t        c_cc[NCCS]; /* control chars */
    speed_t     c_ispeed;   /* input speed */
    speed_t     c_ospeed;   /* output speed */
};


int tcgetattr(int, struct termios *);
int tcsetattr(int, int, const struct termios *);
int tcflush(int, int);

There are three important things to notice about this code. First, we have some typedefs. Then we have a data structure which is used to hold configuration info for the connection. Finally, we have the methods, we're going to use.

Using fiddle's importer, we can almost copy these sections verbatim into our Ruby code. Let's tackle them one by one.

Type aliases and typedefs

In C it's pretty common to create aliases for data types. For example, the termios.h file creates a new type called speed_t that is just a long integer.  The Fiddle importer is able to handle these via its typealias feature.

# equivalent to `typedef unsigned long tcflag_t;`
typealias "tcflag_t", "unsigned long"

Structs

C doesn't have classes or modules. If you want to group a bunch of variables together, you use a struct. Fiddle provides a nice mechanism for working with structs in ruby.

The first step is to define the struct in the Fiddle importer. As you can see, we're almost able to just copy and paste from the header file.

Termios = struct [
  'tcflag_t c_iflag',
  'tcflag_t c_oflag',
  'tcflag_t c_cflag',
  'tcflag_t c_lflag',
  'cc_t         c_cc[20]',
  'speed_t  c_ispeed',
  'speed_t  c_ospeed',
]

Now we can create an "instance" of the struct by using the malloc method. We can set and retrieve values in much  the same way that we would with any normal Ruby class instance.

s = Termios.malloc
s.c_iflag = 12345

Putting it all together

If you combine what we just learned about typedefs and structs with what we've already demonstrated with functions, we can put together a working wrapper of the termios library.

Our wrapper just contains the methods we need to configure the serial port. We'll use plain old ruby for the rest.

require 'fiddle'
require 'fiddle/import'

# Everything in this module was pretty much copied directly from 
# termios.h. In Yosemite it's at /usr/include/sys/termios.h
module Serial
  extend Fiddle::Importer
  dlload '/usr/lib/libSystem.dylib'

  # Type definitions
  typealias "tcflag_t", "unsigned long"
  typealias "speed_t", "unsigned long"
  typealias "cc_t", "char"

  # A structure which will hold configuratin data. Instantiate like
  # so: `Serial::Termios.malloc()`
  Termios = struct [
    'tcflag_t   c_iflag',
    'tcflag_t   c_oflag',
    'tcflag_t   c_cflag',
    'tcflag_t   c_lflag',
    'cc_t         c_cc[20]',
    'speed_t    c_ispeed',
    'speed_t    c_ospeed',
  ]

  # Functions for working with a serial device
  extern 'int   tcgetattr(int, struct termios*)'       # get the config for a serial device
  extern 'int   tcsetattr(int, int, struct termios*)'  # set the config for a serial device
  extern 'int   tcflush(int, int)'                     # flush all buffers in the device

end

Using the library

In the example below we open our serial device in Ruby, then get its file descriptor. We use our termios functions to configure the device for reading. Then we read.

I'm making use of some magic numbers here. These are the result of combining a bunch of bitwise configuration options that are necessary for communication with the arduino. If you're interested in where the magic numbers come from, check out this blog post.

file = open("/dev/cu.wchusbserial1450", 'r')
fd = file.to_i

# Create a new instance of our config structure
config = Serial::Termios.malloc

# Load the default config options into our struct
Serial.tcgetattr(fd, config);

# Set config options important to the arduino. 
# I'm sorry for the magic numbers. 
config.c_ispeed = 9600;
config.c_ospeed = 9600;
config.c_cflag = 51968;
config.c_iflag = 0;
config.c_oflag = 0;

# wait for 12 characters to come in before read returns.
config.c_cc[17] = 12;
# no minimum time to wait before read returns 
config.c_cc[16] = 0;

# Save our configuration
Serial.tcsetattr(fd, 0, config);

# Wait 1 second for the arduino to reboot
sleep(1)

# Remove any existing serial input waiting to be read
Serial.tcflush(fd, 1)

buffer = file.read(12)
puts "#{ buffer.size } bytes: #{ buffer }"

file.close

The serial reader in action

If I now load my arduino with a program that continuously prints hello world, I can use my ruby script to read it.

This arduino program just writes "hello world" to serial, again and again This arduino program just writes "hello world" to serial, again and again

And here's what it looks like when I run my serial monitor.

arduino_serial_output

Get the Honeybadger newsletter

Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo
    Starr Horne

    Starr Horne is a Rubyist and Chief JavaScripter at Honeybadger.io. When she's not neck-deep in other people's bugs, she enjoys making furniture with traditional hand-tools, reading history and brewing beer in her garage in Seattle.

    More articles by Starr Horne
    An advertisement for Honeybadger that reads 'Turn your logs into events.'

    "Splunk-like querying without having to sell my kidneys? nice"

    That’s a direct quote from someone who just saw Honeybadger Insights. It’s a bit like Papertrail or DataDog—but with just the good parts and a reasonable price tag.

    Best of all, Insights logging is available on our free tier as part of a comprehensive monitoring suite including error tracking, uptime monitoring, status pages, and more.

    Start logging for FREE
    Simple 5-minute setup — No credit card required