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.
-
void
- Specifies the return type of the function.
- In this case, it returns nothing so it is void
-
const char *s1
andconst char *s2
- Describes the two strings that can be passed to concat
const
means that the value will not be changed after being passed inchar *parameterName
means that the parameter is a pointer to the first character in a string- This is a
cstring
(more information here). - In Ruby, we put
concat(s1,s2)
since ruby doesn’t allow you to specify the parameter’s type
-
char *result
- A pointer that has enough memory to store the concatenated string
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)
-
#include <string.h>
- Like using require in Ruby.
- Gives access to string functions included in the C standard library
-
strcpy(result, s1);
- Takes s1 string and copies it into the memory location of
result
- “head” would now be stored in
concat_result
- Takes s1 string and copies it into the memory location of
-
strcat(result, s2);
- Takes s2 string and appends it to the end of the string we just copied into
result
- “headphones” would now be stored in
concat_result
- Takes s2 string and appends it to the end of the string we just copied into
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:
-
Application creates the
concat_result
pointer and allocates memory to it -
Application calls the concat function and passes in
concat_result
pointer -
Concat function places the concatenated string in the location of the
concat_result
pointer -
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.
gcc -c -fPIC concat.c -o concat.o
gcc -shared concat.o -o concat.so
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.