Back to Home

Ruby and C. Part 2.

Ruby · C

Ruby and C. Part 2.

    In the last part, we examined the RubyInline library, which allows you to insert C code directly into Ruby methods. It is very convenient to use if we need to speed up several critical methods. But if we need to implement the C library and use it in Ruby, or make a wrapper for an existing one, then the C API will come to our aid to create extensions.


    The Ruby C API provides us with a means of writing Ruby code with C. It sounds a little strange, but let's look at an example.
    Ruby code:
        class test
            def test
                # implementation of the method
            end
        end
    

    Similar C code:
        VALUE test_method (VALUE self) {
            // method implementation
        }
        Init_test () {
            VALUE cTest = rb_define_class ("Test", rb_cObject); // create class Test
            rb_define_method ("test", cTest, test_method, 0); // create test method in class Test
        }
    

    A trivial example is creating a class with one method. Now consider the C code in more detail.
    Using the rb_define_class function, we create the Test class, the descendant of the Object class. Then, using the rb_define_method function, we create the test method of the Test class, and pass the test_method function as an implementation of this method. The last parameter means that our method has no arguments.

    The example uses the VALUE data type everywhere. Any Ruby object, whether it is a string, number, class, module, etc., in C is of type VALUE.
    The Ruby C API provides a set of macros and functions for converting Ruby data types to C and vice versa.
    Here are some of them (from Ruby to C):
        int NUM2INT (VALUE)
        int FIX2INT (VALUE)
        double NUM2DBL (VALUE)
        char * StringValuePtr (VALUE)
    There is also a macro STR2CSTR, but it is better not to use it, quote from the documentation:
    " because STR2CSTR() has a risk of a dangling pointer problem in the to_str() impliclit conversion."

    From C to Ruby:
           VALUE INT2NUM (int)
    	VALUE INT2FIX (int)
    	VALUE rb_str_new2 (char *)
    	VALUE rb_float_new (double) 	
    


    Modules are created similarly to classes:
    VALUE rb_define_module(const char *name)

    Now about methods.
    There are several functions for creating methods. Here is some of them:
    void rb_define_method (VALUE klass, const char * name, VALUE (* func) (), int argc)
    void rb_define_private_method (VALUE klass, const char * name, VALUE (* func) (), int argc)
    
    The arguments are the class, the name of the method, a reference to the function with the implementation of the method, and the number of arguments.
    A function with the implementation of the method should be of the following form:
    VALUE test(VALUE self, VALUE arg1, VALUE arg2, ...)
    self, this is a pointer to an object, it is always present, even if the method has no arguments.
    Depending on the value of the argc argument, the signature may vary. Negative argc values ​​are used to define a method with a variable number of arguments.
    If argc is -1:
    VALUE test(int argc, VALUE *argv, VALUE self)
    argc is the number of arguments, * argv is an array of arguments.
    If argc is -1:
    VALUE test(VALUE args, VALUE self)
    args is a Ruby array with arguments.

    The function Init_test () is called when initializing our extension, in it we just create classes and modules.

    Now, after reviewing the basic features of the Ruby C API, let's try to create our own extension. I decided not to come up with artificial examples and illustrate the creation of the extension with the help of a wrapper for some existing C library. After a long search (almost all popular libraries already have wrappers), the choice fell on libdecodeqr, a library for recognizing QR codes. A simple example for working with it in C can be found here qrtest.c

    Our extension will be very simple, the QRDecoder class with the static decode method.
    The decode method takes the name of a file with a QR code as an argument, and returns a string encoded there.
    Usage example (compare with the code in C :)):
        require "decodeqr"
        puts QRDecoder.decode "test.png"
    

    The task is clear, let's proceed to implementation.
    What we need:
    1. ruby1.8-dev package for building extensions.
    sudo apt-get install ruby1.8-dev
    2. libdecodeqr library.
    sudo apt-get install libdecodeqr libdecodeqr-dev

    Let's start by initializing the extension:
        Init_decodeqr () {
            VALUE cQRDecoder = rb_define_class ("QRDecoder", rb_cObject);
            rb_define_singleton_method (cQRDecoder, "decode", decode, 1);
        }
    

    It's simple, create a QRDecoder class and a static decode method.
    Now the implementation of the decode function:
        VALUE decode (VALUE self, VALUE file) {
            char * file_name = StringValuePtr (file); // convert the Ruby string to C
            // work with the libdecodeqr library, more details can be found in the C example
            QrDecoderHandle qr = qr_decoder_open ();
            IplImage * src = cvLoadImage (file_name, 0);
            qr_decoder_decode_image (qr, src, DEFAULT_ADAPTIVE_TH_SIZE, DEFAULT_ADAPTIVE_TH_DELTA);
            QrCodeHeader * qrh = calloc (sizeof (QrCodeHeader), 1);
            qr_decoder_get_header (qr, qrh);
            char * buf = calloc (qrh-> byte_size + 1, 1);
            qr_decoder_get_body (qr, (unsigned char *) buf, qrh-> byte_size + 1);
            qr_decoder_close (qr);
            return rb_str_new2 (buf); // return the Ruby string
        }
    

    Our extension is ready! The entire code can be viewed here decodqr.c
    Now we need to build our extension. To do this, we use the standard mkmf Ruby library , with which we will generate a Make file.
    Example, extconf.rb file:
        require 'mkmf'
        if have_library ("cv") and # if there is a library "cv"
        have_library ("decodeqr") and # and the library "decodeqr"
        have_library ("highgui") and # as well as "highgui"
        find_header ("highgui.h", "/ usr / include / opencv /") then # and found the header file "highgui.h"
            create_makefile ("decodeqr") # then create a Make file
        else
            puts "oops ..." # otherwise we don't create
        end
    

    And the result of its execution:
    $ ruby ​​extconf.rb
        checking for main () in -lcv ... yes
        checking for main () in -ldecodeqr ... yes
        checking for main () in -lhighgui ... yes
        checking for highgui.h in / usr / include / opencv / ... yes
        creating makefile
    

    And now the standard ones:
        make
        sudo make install
    

    and our extension is ready to use.

    Thus, if you have C code, or want to use the C library, or you want to speed up Ruby, you can simply make an extension and use it in your Ruby programs.

    What I did not touch on in this article:
    1. Working with strings and arrays
    2. Garbage collector
    3. Creating wrappers for C structures

    You can read about this in the links below:
    1. The README.EXT file that comes with the Ruby distribution.
    2. The head of Extending Ruby in the famous pickaxe
    3. This wonderful blog maxidoors.ru

    For those interested, the libdecodeqr library: trac.koka-in.org/libdecodeqr
    Caution! Everything is in Japanese there :) The documentation can be viewed directly in the header file:
    trac.koka-in.org/libdecodeqr/browser/tags/release-0.9.3/src/libdecodeqr/decodeqr.h

    In the next part I will talk about using Ruby in as a scripting language in C / C ++ applications.

    Read Next