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

Tagged , . Bookmark the permalink.

Leave a Reply