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.rb | Description |
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
