In the last
part, we looked at 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 Ruby, or make a wrapper for an existing one, then the C API will come to the rescue to create extensions.
The Ruby C API provides us with tools for writing Ruby code with C. It sounds a bit strange, but let's look at an example.
Ruby code:
class Test
def test
# method implementation
end
end
Similar C code:
VALUE test_method (VALUE self) {
// implementation of the method
}
Init_test () {
VALUE cTest = rb_define_class ("Test", rb_cObject); // create test class
rb_define_method ("test", cTest, test_method, 0); // create a test method in the Test class
}
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 class Test, a successor of the Object class. Then, using the rb_define_method function, we create a test method of the Test class, and as an implementation of this method, we pass the test_method function. The last parameter means that our method has no arguments.
The example uses the VALUE data type everywhere. Any Ruby object, be it a string, a number, a class, a 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 like classes:
VALUE rb_define_module(const char *name)
Now about the methods.
There are several functions for creating methods. Here are 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)
Arguments are class, method name, function reference with method implementation, and the number of arguments.
The function with the implementation of the method should be of the following form:
VALUE test(VALUE self, VALUE arg1, VALUE arg2, ...)
self, 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 - Ruby array with arguments.
The Init_test () function is called when initializing our extension; in it we just create classes and modules.
Now, after considering the main features of the Ruby C API, let's try to create our own extension. I decided not to invent artificial examples and illustrate creating an extension using 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 to work with it in C can be found here
qrtest.cOur extension will be very simple, the QRDecoder class with the static decode method.
The decode method takes the name of the file with the QR code as an argument and returns the encoded string there.
Example of use (compare with the code in C :)):
require "decodeqr"
puts QRDecoder.decode "test.png"
The task is clear, let's get down 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
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 ruby string to C
// work with libdecodeqr library, you can see in more detail in the 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 a ruby string
}
Our extension is ready! The complete code can be found here
decodqr.cNow we need to build our extension. To do this, we use the standard Ruby library
mkmf , with which we will generate the 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 "decodeqr" library
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 do not create
end
And the result of its implementation:
$ 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.
So, if you have C code, or you 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 it in the links below:
1. The README.EXT file that comes with the Ruby distribution.
2. Chapter Extending Ruby in famous
pickaxe3. This wonderful blog
maxidoors.ruFor those interested, the libdecodeqr library:
trac.koka-in.org/libdecodeqrCaution! There everything is in Japanese :) Documentation can be viewed directly in the header file:
trac.koka-in.org/libdecodeqr/browser/tags/release-0.9.3/src/libdecodeqr/decodeqr.hIn the next part, I will talk about using Ruby as a scripting language in C / C ++ applications.