Ruby and C. Part 2.
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-dev2. libdecodeqr library.
sudo apt-get install libdecodeqr libdecodeqr-devLet'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.