Page Content

Tutorials

Embedding Ruby: Running Ruby Code Inside C & C++ Programs

Embedding Ruby

Developers can immediately integrate the Ruby interpreter and its features into another application usually one built in a lower-level language like C language or C++ by embedding Ruby. By allowing the C application to run Ruby code, this feature reverses the “extending Ruby with C” connection.

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

Approaches to Embedding Ruby

The Ruby interpreter can be embedded in two main ways:

Interpreter Takeover (ruby_run)

The simplest method involves the C application initializing the Ruby interpreter and then giving it complete control. The drawback is that the process flow is essentially taken over by the interpreter, which never returns from the ruby_run function.

Example 1: Basic Interpreter Takeover (C Code)

#include "ruby.h"

int main(int argc, char **argv)
{
    ruby_init();               // Initialize the Ruby VM
    ruby_init_loadpath();      // Setup load paths
    ruby_script("embedded");   // Set $0
    
    int state = 0;
    rb_load_protect(rb_str_new_cstr("start.rb"), 0, &state);

    if (state) {
        // Print Ruby exception if script fails
        VALUE err = rb_errinfo();
        rb_p(err);
        return 1;
    }

    ruby_finalize();           // Clean shutdown
    return 0;
}

You can also read Ruby GUI Toolkits: GTK, wxRuby And RubyCocoa Explained

Dialogue and Method Invocation

This sophisticated technique enables a dynamic interaction between the C application and the Ruby code by allowing the C code to call particular Ruby methods and retake control as soon as they return. To do this, the interpreter must be initialized without using the ruby_run method.

All calls to Ruby methods need to be protected using rb_protect since Ruby code has the ability to raise exceptions, which can result in the C program terminating.

Example 2: Protected Method Invocation (C Code)

The C functions required to invoke a Ruby method (sum) defined in a Ruby class (Summer) and manage any exceptions are demonstrated in this example:

Ruby Class Definition (e.g., sum.rb):

class Summer
  def sum(max)
    raise "Invalid maximum #{max}" if max < 0
    (max * max + max) / 2
  end
end

# Use the class and print a result
s = Summer.new
puts s.sum(10)

Output

55

C Wrapper and Protection Functions: To run the Ruby method, the C code needs to write wrapper functions (wrap_sum) and utilize rb_protect to catch exceptions (protected_sum).

#include "ruby.h"

static ID id_sum;   // method ID for "sum"

// Wrapper function to invoke the Ruby method (called by rb_protect)
static VALUE wrap_sum(VALUE args) {
    VALUE *values = (VALUE *)args;
    VALUE summer = values[0];
    VALUE max    = values[1];

    return rb_funcall(summer, id_sum, 1, max);
}

// Protection function that calls the wrapper and handles errors
static VALUE protected_sum(VALUE summer, VALUE max) {
    int error;
    VALUE args[2];

    args[0] = summer;
    args[1] = max;

    VALUE result = rb_protect(wrap_sum, (VALUE)args, &error);

    return error ? Qnil : result;
}

int main(void) {
    ruby_init();
    ruby_init_loadpath();

    // Load Ruby file that defines class Summer
    rb_require("sum.rb");

    // Get ID for method "sum"
    id_sum = rb_intern("sum");

    // Fetch class Summer
    VALUE klass = rb_const_get(rb_cObject, rb_intern("Summer"));

    // Create instance: Summer.new
    VALUE summer = rb_class_new_instance(0, NULL, klass);

    // Call the protected method
    VALUE result = protected_sum(summer, INT2NUM(5));

    // Print result
    if (NIL_P(result)) {
        printf("Error: Ruby exception occurred.\n");
    } else {
        printf("Result: %ld\n", NUM2LONG(result));
    }

    ruby_finalize();
    return 0;
}

Constraints and Requirements

Initialization: Ruby_init() must be called before any Ruby-related functions can be called. On some systems, platform-specific preparation can be necessary prior to ruby_init().

Compilation: The C application has to link to the required Ruby library files and have access to the Ruby header files in order to compile the embedded code.

Thread Safety: Because it stores state in global variables, the Ruby interpreter is not thread-safe and was not designed with embedding in mind. Each process can only have one embedded interpreter.

C API Access: A major component of embedded programs is the Ruby C Language API. Among the primary functions utilized are:

  • rb_require(const char *name): Similar to the require statement in Ruby.
  • rb_funcall(...): Used to call an object’s Ruby method.
  • rb_intern(char *name): Gives back the ID for a specified Ruby symbol or method name.
  • ruby_finalize(): Stops the interpreter from working.

You can also read Extending Ruby with C: A Complete Beginner’s Guide

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