Compiling

Compiling C code to be loaded as a Ruby extension requires some fancy compiler options. Ruby’s mkmf stdlib makes it easy to do this by generating an appropriate makefile for you.

Preparation

First create the file extconf.rb in the same directory as your C code:

#!/usr/bin/env ruby
require 'mkmf'

# preparation for compilation goes here

create_header
create_makefile 'foobar'

The preparation section should perform actions similar to the standard UNIX configure script e.g.

  • Check features of the current platform
  • Check for existence of required libraries and functions
  • Check for programs needed for building

The most common of these actions are provided by mkmf (but you have all of Ruby at your disposal if you need it). For example, an extension which uses SDL2 and needs to know how big ints are might call:

check_sizeof('int')
have_library('SDL2')
have_func('SDL_Init', 'SDL2/SDL.h')

create_header creates the file extconf.h containing preprocessor definitions based on the results of the mkmf functions you called previously. For this example, extconf.h might contain

#define SIZEOF_INT 4
#define HAVE_SDL_INIT 1

This header should be included in your C files so that you can adapt your code to a variety of platforms. Note that you can and should abort the extconf.rb script if a mkmf function returns a value that indicates that the build will fail. For example, if SDL2 is a requirement of your extension you should exit with some meaningful error message if have_library('SDL2') returns false. This is preferred to generating the Makefile anyway and leaving the user with an opaque compiler error.

create_makefile obviously creates the Makefile, but its argument is especially important: it defines the entry point of your C code, the name of the compiled library, and thus the argument for require in Ruby! This should be the name of your extension.

You can modify a few of the generated Makefile variables by modifying the corresponding globals in Ruby: $CFLAGS, $CPPFLAGS, and $LDFLAGS1. You can also use the $objs global to define a list of object files for the Makefile if it’s method of automatically generating targets doesn’t work for your extension.

All of the mkmf functions and their options are well-documented online. In addition to the functions for generating extconf.h, there are a variety of functions for handling different source file layouts, different file dependencies, etc.

Execution

The rest couldn’t be simpler

$ ruby extconf.rb
$ make

2But of course that won’t do anything without some C code to compile.

Init

In your C code, you’ll want to include ruby.h to access the API. Other than that the only requirement is to define a function for the Ruby VM to call when your library is required. The name of the function is determined by the argument you passed to create_makefile in extconf.rb. We used “foobar” in our example, so we’ll create foobar.c containing

#include <ruby.h>
#include "extconf.h"

void Init_foobar()
{
	/* code run by `require` */
}

Filenames

If your extension has only a single C file, you should name it after your extension as we did above. If your extensions has multiple C files do not name any of them after your extension. This is because the Makefile may generate a .o file named after your extension for the linking stage, which would cause a conflict if you also have a .c file which compiles to that filename.

Also avoid naming any files conftest.c as this file may be written to by mkmf.

Success

Now make should compile a .so (or some other library) file which you can require in Ruby. You can fill in your Init function with plain C code, but you’ll probably want to go back and learn about the C API to do more interesting stuff.

Gem

After you’ve got your extension working nicely, you may want to bundle it up as an easily distributable Ruby gem. rubygems.org has a detailed guide on creating gems, but as far as C extensions go you just need to tell the spec about extconf.rb3:

Gem::Specification.new do |spec|
  # usual gem spec stuff...

  spec.extensions = ['ext/extconf.rb']
end

If your gem includes multiple independent extensions, you can organize them in subdirectories of ext/ and pass all of the extconf.rbs to the spec.

Footnotes

  1. Check the official documentation

  2. The official documentation hints that mkmf parses certain command line flags e.g. --vendor. But I can’t find this documented anywhere. 

  3. http://guides.rubygems.org/gems-with-extensions/ 

Comments