Page Content

Tutorials

Extending Ruby with C: A Complete Beginner’s Guide

Extending Ruby with C

Using a lower-level language, such as C language, to implement portions of a Ruby application is known as “extending Ruby with C.” This is usually done to use platform-specific code, improve performance (making a portion of the application run faster), or use pre-existing C libraries for which Ruby bindings are not yet available. Writing C extensions in Ruby is not as difficult as it is in other dynamic languages.

Three primary methods for using C to augment Ruby:

  • Directly writing a C extension with the Ruby C Language API (the manual method).
  • Applying Ruby classes and functions to a C library (manually wrapping).
  • RubyInline (the automated way) and similar programs allow you to write Inline C right within Ruby code.

Ruby’s require statement can be used to load both C and binary extensions. Additionally, C extensions can be produced as part of the gem installation process and included in a Ruby gem.

Writing a C Extension for Ruby (Manual API)

To write a C extension, you need two general pieces: the C code itself and the extension configuration file. You will need the Ruby header files installed on your system.

The Extension Configuration (extconf.rb)

This file is a simple Ruby program that determines system features and generates a Makefile customized for the target platform. It is conventionally named extconf.rb.

File: extconf.rbDescription
require 'mkmf'Loads the library used to create Makefiles.
create_makefile('example')Specifies the name of the output extension (e.g., example.so or example.bundle).

The extconf.rb extension configuration

You can also read TK GUI in Ruby: Creating Your First Graphical User Interface

The C Source Code

The ruby.h header file contains functions and macros that are used in the C source code to define modules, classes, and methods that are available in Ruby. These are the essential elements:

VALUE: Any Ruby object can be referenced by this, which is the C counterpart of a reference.

Initialization Function: The C global function Init_name (for example, Init_example) must be defined by every extension since it is the entry point that the Ruby interpreter calls when the extension is loaded.

Defining Structures: Ruby’s class hierarchy and namespace are defined with functions such as rb_define_module and rb_define_class_under.

Defining Methods: A Ruby method name can be bound to a C function using rb_define_method or rb_define_singleton_method.

Code Example: Simple C Extension (example.c)

This C library defines a class called Example::Class, a Ruby module called Example, and a function called print_string that covers C’s printf.

#include <ruby.h>
#include <stdio.h>
// Static variables to hold Ruby object references (VALUEs)
static VALUE rb_mExample;
static VALUE rb_cClass;

// C function implementing the Ruby method Example::Class#print_string(s)
// It accepts two VALUE arguments: 'class' (self) and 'arg' (the string 's')
static VALUE print_string(VALUE class, VALUE arg) {
  // RSTRING(arg)->ptr extracts the C string pointer from the Ruby VALUE
  printf("%s", RSTRING(arg)->ptr);
  return Qnil; // All C functions callable from Ruby must return a VALUE
}

// The initialization function (must match the name in extconf.rb)
void Init_example( ) {
  // Define the Ruby module "Example"
  rb_mExample = rb_define_module("Example"); 
  
  // Define the class "Class" under the Example module, inheriting from Object (rb_cObject)
  rb_cClass = rb_define_class_under(rb_mExample, "Class", rb_cObject); 
  
  // Define the instance method "print_string" which takes 1 Ruby argument
  rb_define_method(rb_cClass, "print_string", print_string, 1); 
}

Compilation and Usage

The command line is used to build the extension following the creation of the C and configuration files:

$ ruby extconf.rb
$ make 

This creates a shared object file (platform equivalent, example.so).

Ruby Usage:

require 'example'
e = Example::Class.new
e.print_string("Hello World\n") # Hello World

Wrapping C Structures

Ruby objects are used to hold data that is unique to the C code of your extension, like a file handle or a custom C struct.

Data_Wrap_Struct: Contains a Ruby object (VALUE) wrapped around a C data structure.

Data_Get_Struct: Retrieves the Ruby object’s (VALUE) pointer to the C data structure.

Usually, Data_Wrap_Struct is used inside the constructor-called allocator function of your class, while Data_Get_Struct is used inside its instance methods. In particular, if the class encapsulates a C structure, the allocation method is in charge of creating the memory that your object uses.

Code Example: Wrapping a C Struct (Simplified)

This example demonstrates how to wrap a basic example_struct using the C specification.

#include <ruby.h>
#include <string.h>

// Define the C structure
typedef struct example_struct {
    char *name;
} example_struct;

// --- GC mark & free functions ---

static void example_struct_free(void *ptr) {
    example_struct *p = (example_struct *)ptr;
    if (p) {
        if (p->name) free(p->name);
        free(p);
    }
}

static size_t example_struct_size(const void *ptr) {
    return sizeof(example_struct);
}

// TypedData structure definition for GC
static const rb_data_type_t example_struct_type = {
    "ExampleStruct",
    { NULL, example_struct_free, example_struct_size },
    NULL, NULL,
    RUBY_TYPED_FREE_IMMEDIATELY
};

// Allocation function
static VALUE rb_example_struct_alloc(VALUE klass) {
    example_struct *p = ALLOC(example_struct);
    p->name = NULL;
    return TypedData_Wrap_Struct(klass, &example_struct_type, p);
}

// initialize(name)
static VALUE rb_example_struct_init(VALUE self, VALUE name) {
    example_struct *p;
    Check_Type(name, T_STRING);

    TypedData_Get_Struct(self, example_struct, &example_struct_type, p);

    p->name = malloc(RSTRING_LEN(name) + 1);
    memcpy(p->name, StringValuePtr(name), RSTRING_LEN(name) + 1);

    return self;
}

// Example#name — return Ruby string
static VALUE rb_example_struct_name(VALUE self) {
    example_struct *p;
    TypedData_Get_Struct(self, example_struct, &example_struct_type, p);
    return rb_str_new_cstr(p->name);
}

// Initialization function
void Init_example() {
    VALUE mExample = rb_define_module("Example");
    VALUE cStruct = rb_define_class_under(mExample, "Struct", rb_cObject);

    rb_define_alloc_func(cStruct, rb_example_struct_alloc);
    rb_define_method(cStruct, "initialize", rb_example_struct_init, 1);
    rb_define_method(cStruct, "name", rb_example_struct_name, 0);
}

This is how the resulting Ruby object usage would appear: test_struct = for instance::Struct.new("Test Struct").

You can also read Concurrency In Ruby: Knowing Threads And Parallel Execution

Writing Inline C with RubyInline

RubyInline can be used to implement minor parts of your software in C without having to deal with creating a separate C extension project.

Mechanism: C code can be embedded directly into your Ruby program with RubyInline, which is accessible as the rubyinline gem. The extension is automatically created, compiled, and loaded into your application.

Behavior: A builder object is returned by the Module#inline method that is defined. The C code is passed to this builder as a string.

Code Example: Inline C File Copy

The Copier class is defined in this example, and the instance method copy_file is fully implemented in C code that is embedded in the Ruby class declaration.

#!/usr/bin/ruby -w
# copy.rb
require 'rubygems'
require 'inline'

class Copier
  inline do |builder|
    builder.c <<-END
      void copy_file(const char *source, const char *dest)
      {
        FILE *source_f = fopen(source, "rb");
        if (!source_f) {
          rb_raise(rb_eIOError, "Could not open source: '%s'", source);
        }

        FILE *dest_f = fopen(dest, "wb");
        if (!dest_f) {
          fclose(source_f);
          rb_raise(rb_eIOError, "Could not open destination: '%s'", dest);
        }

        char buffer[1024];
        size_t nread = fread(buffer, 1, sizeof(buffer), source_f);

        while (nread > 0) {
          fwrite(buffer, 1, nread, dest_f);
          nread = fread(buffer, 1, sizeof(buffer), source_f);
        }

        fclose(source_f);
        fclose(dest_f);
      }
    END
  end
end

# Usage:
open('source.txt', 'w') { |f| f << 'Some text.' }
Copier.new.copy_file('source.txt', 'dest.txt')
puts open('dest.txt') { |f| f.read }

The C function copy_file is generated as an instance method of Copier and invoked when this script executes. Note that RubyInline cannot be used from inside of irb.

Security and Best Practices

Because a C code error can cause the Ruby interpreter to crash because it operates outside of its safety safeguards, you must exercise caution when creating C-level code. It is essential to set the Ruby safety level to at least SAFE = 1 when sharing code over a network in order to stop malicious code execution.

Before and after building C code, benchmarking is crucial to make sure the performance gain outweighs the complexity. Bugs in C extensions are far harder to fix than in pure Ruby code; frequently, a fix in the original C source and a recompile are needed.

You can also read What Is Cookies And Sessions In Ruby, key Differences Of It

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index