A tale of C++ native Ruby and RAII

While I was playing with my last project, I wanted to gather the execution time of a few native functions. Doing so in C is a little bit painful: it requires quite a bit of code, temporary variable, and such. One really powerful idiom I liked using while doing C++ was Resource Acquisition Is Initialization (RAII) to do this kind of task. This post will contain two subjects: using RAII to time function execution and using rake-compiler with C++ for native Ruby. So here is a tale of C++ native Ruby and RAII.

What is RAII

In a class, RAII is represented in a really simple way: the constructor acquires something (file handle, lock, etc.) and the destructor releases it.

An example of this is the std::ofstream class. An instance of this class acquires a handle on the given file and the destructor releases it. Therefore, the following is completely valid:

This idiom ensures that the underlying resource gets released correctly in all cases. No need to catch exceptions and rethrow in order to manually close the file, it will be done when the stack unwinds.

Using RAII to time function execution

In order to use RAII to time function execution, I went with a really simple flow:

  • The constructor gets the start time
  • The destructor gets the end time and prints the elapsed time

The code using the previous flow can be found here:

Using this class becomes super simple: create an instance and let it go out of scope. An example of this can be found in this file:

Using rake-compiler with C++

I was expecting some major differences when using C++ instead of C with rake-compiler. Turns out that the tool does most of the heavy lifting. I only needed to put the C++ files in the directory and they got built magically.

There were only a few things that I needed to care about:

  • Ensure that the entrypoints were in an Extern C section in order for the method signatures to be valid
  • Define a typedef to enforce C signatures whenever you call a C Ruby function that requires a function pointer
  • In order to manage memory correctly, remember to delete any allocated instances

Using rake-compiler to build native Ruby extensions

Having renewed my love for my Raspberry Pi, I wanted to make a quick library to access a temperature sensor through Ruby. This sensor uses a single-bus format, and one communication lasts about 4ms. I felt like this was good enough of an excuse to play with the native layer of Ruby. Here is how I ended up using rake-compiler to build native Ruby extensions.

Quick disclaimer: most of what I did was heavily inspired by a RubyGems guide. I’ll try to point out things that I had most issues with or that I found particularly interesting. All the code associated with this post is hosted on my GitHub project.

Adding a dependency on an external library

Adding a dependency on an external library was one of the things that were not completely clear. In my case, I needed to use libgpiod. In order to do so, I had to modify extconf.rb to add the following lines:

These two lines ensure that the generated Makefile will include the right search directories for the headers and link to the right library.

Controlling the native classes’ modules

I ended up with a really simple solution for handling my modules: the module is created in Ruby, and then fetched in C and used there. In the sample project, the module creation is done in the main Ruby file and then used when initializing the C module.

Getting the module was done easily: rb_const_get(rb_cObject, rb_intern("NCI")). Once this was done, creating the class was done using the handy rb_define_class_underrb_define_class_under(mNCI, "NCINativeDevice", rb_cObject). The third parameter to this last function is the superclass, in this case the Object class.

Associating a C structure with the Ruby instance

Three functions come into play when using a C structure: nci_native_device_allocnci_native_device_init, and nci_native_device_free.

The first function (_alloc) is used to allocate an empty C structure. This function is not the initializer, it will not get any of the arguments passed on initialization. The important part of the function is the call to Data_Make_Struct. This function will allocate the given C structure, but won’t initialize it for you.

The second function (_init) is the true initializer. It will receive the arguments that are passed in the Ruby code. In order to store these values in the C structure, you need to extract the C pointer from the instance. This is done using Data_Get_Struct. Weirdly enough, this won’t return the pointer to the C structure, it stores it in the last argument.

The last function (_free) is the most important of all. It is responsible for deallocating whatever you allocated. The C structure (allocated for you by Data_Make_Struct) does not need to be deallocated, the system will do it for you.

Various sources