Jertype

Member of Information Superhighway

Creating C shared libraries for ruby FFI

If you’re a ruby programmer, why is it helpful to learn C? Many ruby gems use C in order to communicate with system libraries or to optimize for performance.

We’ll be building our own C library that can be called by C, Ruby, or any other language with a Foreign Function Interface (FFI).

If you’re familiar with C or don’t want to hear the details, go ahead and skip to the next post.

Prerequisites

Ruby and GCC are installed (Should already be done on Linux and MacOS). For Windows you’ll want to install Ruby+Devkit 2.4.4-1 (x64).

What we’re creating

We are going to create a function called concat that takes one string and combines it with another string. For example, if you concatenated “rain " and “forest” together, the result would be “rain forest”.

Here’s the ruby version of that:

def concat(s1, s2)
  s1 + s2
end

In C, creating this function gets a bit more complicated.

Defining the Function

In a C library, you’ll use a header file to describe all the functions that will be available in your library. We are going to create a function called concat, so let’s create a file called concat.h and put in the following contents.

concat.h

void concat(const char *s1, const char *s2, char *result);

This doesn’t look too different from Ruby. Let’s look at the parts that may look unfamiliar.

When we refer to a pointer we are referring to a location in your computer’s memory.

Imagine that the memory in your computer is stored in the form of a bunch of blocks and the pointer tells the program which block to start storing the string in.

When we call this function, we’ll need to pass in a pointer that will store the result of the function.

Implementation

Next, let’s create our full implementation of the concat function.

concat.c

#include <string.h>

void concat(const char *s1, const char *s2, char *result) {
  strcpy(result, s1);
  strcat(result, s2);
}

Let’s break it down, assuming that we called concat with the following arguments, and that concat_result is a pointer.

concat("head", "phones", concat_result)

If you’re used to ruby, this function may be a little confusing. After all, we take in two strings, and put their contents into the concat_result variable. Then the function ends and we never return it.

However, the application that passed concat_result into this function will now be able to read “headphones” from the concat_result variable on their end.

Below is a breakdown of what happens:

  1. Application creates the concat_result pointer and allocates memory to it

  2. Application calls the concat function and passes in concat_result pointer

  3. Concat function places the concatenated string in the location of the concat_result pointer

  4. Application has the concatenated string available in concat_result without the function returning it.

By sending a pointer to the function, the function can change the data located at the pointer that will be readable by the caller, even if it is not returned by the function.

Compiling the C library

C is different from Ruby in that it needs to be compiled before it can be run. In order to compile your application, check to see if you have gcc installed by running gcc -v. If you’re on MacOS, it should prompt you to install command line developer tools that are required to compile C programs.

Make sure your concat.h and concat.c files are in the same folder, then run the following commands.

If no errors appeared, congratulations! You’ve built a C library that has a string concatenation function in it. The concat.so file you created is what can be loaded by other applications to use your concat function.

In Summary

That was a lot of work just to concatenate some strings wasn’t it? In our next post, we’ll be calling the concat library from a Ruby application.