UNIT_II_Extending Ruby
UNIT_II_Extending Ruby
Writing Ruby in C
1. class Test: This line begins the definition of a new class named Test. In
Ruby, class names are capitalized by convention.
2. def initialize: This is a special method in Ruby classes. It serves as the
constructor and is automatically called when a new object of the class is
created using Test.new. In this method, an instance variable @arr is
initialized to a new empty array using Array.new. Instance variables in
Ruby are denoted by the @ symbol and are accessible throughout the
class.
3. def add(anObject): This defines an instance method named add, which
takes one parameter anObject. Inside this method, the parameter
anObject is pushed into the @arr array using the push method. This
method appends the given object to the end of the array.
So, the purpose of this Test class is to provide a container for storing objects in
an array. The initialize method ensures that each instance of Test starts with an
empty array, and the add method allows adding objects to this array. Here's an
example of how you might use this class:
test_instance = Test.new
test_instance.add("Hello")
test_instance.add(42)
test_instance.add([1, 2, 3])
puts test_instance.inspect
Output:
#include "ruby.h"
static VALUE t_init(VALUE self)
{
VALUE arr; arr = rb_ary_new();
rb_iv_set(self, "@arr", arr);
return self;
}
VALUE cTest;
void Init_Test()
{
cTest = rb_define_class("Test", rb_cObject);
rb_define_method(cTest, "initialize", t_init, 0);
rb_define_method(cTest, "add", t_add, 1);
}
This code is a C extension for Ruby that defines a class named Test with two
methods: initialize and add. Let's break down each part of the code:
#include "ruby.h"
This line includes the Ruby header file, ruby.h, which provides access to Ruby's C
API.
VALUE arr;
arr = rb_ary_new();
return self;
}
This function t_init is the implementation of the initialize method for the Test
class. It takes one argument, self, which represents the instance of the class
being initialized. Inside the function, a new array object arr is created using the
rb_ary_new() function, which is a Ruby C API function to create a new array
object. Then, rb_iv_set is used to set an instance variable @arr for the object
referenced by self. Finally, it returns self, which is a common practice in Ruby C
extensions to indicate successful initialization.
VALUE arr;
rb_ary_push(arr, anObject);
return arr;
}
This function t_add is the implementation of the add method for the Test class. It takes two
arguments: self, which represents the instance of the class invoking the method, and
anObject, which represents the object to be added to the array. Inside the function, rb_iv_get
is used to retrieve the instance variable @arr from the object referenced by self. Then,
rb_ary_push is used to push the anObject onto the array referenced by arr. Finally, it
returns the modified array.
VALUE cTest;
void Init_Test() {
This part of the code initializes the Test class and registers its methods with Ruby's
interpreter. In the Init_Test function, rb_define_class is used to define the Test class, which
inherits from Object (rb_cObject). Then, rb_define_method is used to define the initialize
and add methods for the Test class, associating them with the t_init and t_add C functions,
respectively.
Overall, this C extension allows Ruby developers to create instances of the Test class with an
@arr instance variable initialized to an empty array, and provides a method add to add
objects to this array. This extension demonstrates how to interact with Ruby objects and
define Ruby classes and methods using C code.
Memory Allocation
In order to work correctly with the garbage collector, you should use the
following memory allocation routines.
These routines do a little bit more work than the standard malloc.
In Ruby, ALLOC_N, ALLOC, and ALLOCA_N are macros used for memory
allocation within C extensions. These macros are part of Ruby's C API and
are used when creating objects that need to be managed by Ruby's
garbage collector.
allocated memory.
type *ALLOC_N(type, n)
For example:
VALUE *array = ALLOC_N(VALUE, 10); // Allocates memory for 10 VALUE objects
type *ALLOC(type)
For example:
VALUE string = ALLOC(VALUE); // Allocates memory for a single VALUE object
type *ALLOCA_N(type, n)
For example:
VALUE *array = ALLOCA_N(VALUE, 10); // Allocates memory for 10 VALUE
objects on the stack
ALLOC_N and ALLOC allocate memory on the heap, which means you are
responsible for freeing the memory when it is no longer needed, typically using
free(). On the other hand, ALLOCA_N allocates memory on the stack, which is
automatically reclaimed when the function exits. Therefore, memory allocated
with ALLOCA_N does not need to be explicitly freed. However, stack memory is
limited and can lead to stack overflow if excessively used.
Creating an Ruby Extension
Having written the source code for an extension, we now need to compile it so Ruby
can use it.
We can either do this as a shared object, which is dynamically loaded at runtime, or
statically link the extension into the main Ruby interpreter itself.
1. Create the C Source Code File(s): Write the C code for your
extension. This code typically contains functions that interact with
Ruby's C API to define classes, methods, and other functionality.
Save the C code in one or more .c files in your directory.
2. Create extconf.rb: extconf.rb is a Ruby script that configures the
build process for your extension. It typically checks for necessary
libraries and headers, sets compiler flags, and generates a Makefile
based on the system's configuration. Here's a basic example of what
extconf.rb might look like:
require 'mkmf'
# Generate Makefile
create_makefile('my_extension/my_extension')
This script uses Ruby's mkmf library to perform checks and generate
the Makefile needed to build the extension.
3. Run extconf.rb: In the terminal, navigate to the directory
containing your extension files and run extconf.rb:
ruby extconf.rb
This will execute the extconf.rb script, which will perform necessary
checks and generate a Makefile based on the results.
4. Run make: After extconf.rb has generated the Makefile, you can
run make to compile your C code into a shared object library ( .so file
on Unix-like systems, .dll file on Windows):
make
This command compiles the C code and produces the compiled
extension.
5. Run make install: Once the extension is compiled, you can
optionally install it to your Ruby environment. Running make install
will typically copy the compiled shared object file to the appropriate
location where Ruby can load it.
make install
This command installs the compiled extension, making it available
for use in Ruby scripts.
After completing these steps, your Ruby extension should be ready for
use. You can require it in your Ruby scripts like any other library and use
the functionality provided by the extension.
Embedding Ruby to Other Languages
There are several ways to embed Ruby into other languages, but one of
the most common methods is using the C API provided by Ruby itself.
Below is a detailed explanation of how Ruby can be embedded into C,
which is a common scenario:
2. Initialization:
arbitrary Ruby code and obtain the results back into your C
application.
You can create and manipulate Ruby objects from within your C
application using the functions provided by the Ruby C API. For
example, you can create new instances of Ruby classes, call
methods on Ruby objects, and manipulate their attributes.
5. Error Handling:
6. Finalization:
Once you're done using the Ruby interpreter, you should finalize it
by calling ruby_cleanup(). This releases any resources allocated by
the Ruby interpreter and shuts down the Ruby runtime environment.
Example:
int main() {
// Initialize the Ruby interpreter
ruby_init();
return 0;
}
This C code demonstrates embedding Ruby code within a C program. Let's
break it down step by step:
Besides C, Ruby can also be embedded into other languages like C++,
Python, and even JavaScript through various language bindings and
extensions. These bindings typically wrap the Ruby C API with language-
specific constructs, making it easier to embed Ruby code into applications
written in those languages.
Advantages:
Challenges:
Integration Overhead: Embedding Ruby adds complexity to your
application, especially if your codebase is primarily written in
another language. Managing the interaction between the host
application and the embedded Ruby code requires careful design
and implementation.
Performance Overhead: There may be performance implications
when embedding an interpreted language like Ruby into a native
application, particularly in performance-critical scenarios. Careful
optimization and profiling may be necessary to mitigate these
concerns.
Dependencies: Embedding Ruby adds dependencies to your
application, as it requires the Ruby interpreter to be installed on the
target system. Managing these dependencies across different
platforms can be challenging.
#include <ruby.h>
ruby_init();
3. Evaluate Ruby Code
Once the Ruby interpreter is initialized, you can execute Ruby code from
within your C program. The rb_eval_string() function is used to evaluate a
string containing Ruby code. This function takes a string of Ruby code as
its argument and executes it.
ruby_cleanup(0);
return 0;
Embedding a Ruby interpreter into a C program involves initializing the
interpreter, executing Ruby code, finalizing the interpreter, and returning from
the main function. This allows you to seamlessly integrate Ruby functionality into
your C programs, enabling you to leverage the strengths of both languages
within a single application.