Pull to refresh

Ruby и C. Часть 2.

Ruby
В прошлой части мы рассмотрели библиотеку RubyInline, которая позваляет делать вставки C кода прямо в Ruby методы. Ее очень удобно использовать если нам нужно ускорить несколько критичных методов. Но если нам нужно реализовать C библиотеку и использовать в Ruby, или сделать обертку для уже существующей, то нам на помощь придет C API для создания расширений.


Ruby C API предоставляет нам средства написания Ruby кода с помощью C. Звучит немного странно, но давайте рассмотримм пример.
Ruby код:
    class Test
        def test
            #реализация метода
        end
    end

Аналогичный C код:
    VALUE test_method(VALUE self) {
        //реализация метода
    }

    Init_test() {
        VALUE cTest = rb_define_class("Test", rb_cObject); //создание класса Test
        rb_define_method("test", cTest, test_method, 0); //создание метода test в классе Test
    }


Тривиальный пример — создание класса с одним методом. Теперь рассмотрим подробнее C код.
С помощью функции rb_define_class мы создаем класс Test наследник класса Object. Затем с помощью функции rb_define_method мы создаем метод test класса Test, и в качестве реализации этого метода передаем функцию test_method. Последний параметр означает что наш метод не имеет аргументов.

В примере везде используется тип данных VALUE. Любой Ruby объект, будь то строка, число, класс, модуль и т.п., в C имеет тип VALUE.
Ruby C API предоставляет набор макросов и функции для конвертации типов данных Ruby в C и наоборот.
Вот некоторые из них(из Ruby в С):
    int NUM2INT(VALUE)
    int FIX2INT(VALUE)
    double NUM2DBL(VALUE)
    char *StringValuePtr(VALUE)
Также есть макрос STR2CSTR, но его лучше не использовать, цитата из документации:
"because STR2CSTR() has a risk of a dangling pointer problem in the to_str() impliclit conversion."

Из С в Ruby:
       VALUE INT2NUM(int)
	VALUE INT2FIX(int)
	VALUE rb_str_new2(char *)
	VALUE rb_float_new(double) 	


Модули создаются аналогично классам:
VALUE rb_define_module(const char *name)

Теперь о методах.
Есть несколько функций для создания методов. Вот некоторые из них:
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)
Аргументами являются класс, имя метода, ссылка на функцию с реализацией метода, и количество аргументов.
Функция с реализацией метода должна быть следующего вида:
VALUE test(VALUE self, VALUE arg1, VALUE arg2, ...)
self, это указатель на объект, он присутствует всегда, даже если у метода нет аргументов.
В зависимости от значения аргумента argc, сигнатура может меняться. Отрицательные значения argc используются для определения метода с переменным числом аргументов.
Если argc равен -1:
VALUE test(int argc, VALUE *argv, VALUE self)
argc — количество аргументов, *argv — массив аргументов.
Если argc равен -1:
VALUE test(VALUE args, VALUE self)
args — Ruby массив с аргументами.

Функция Init_test() вызывается при инициализации нашего расширения, в ней мы как раз создаем классы и модули.

Теперь, после рассмотрения основных возможностей Ruby C API, давайте попробуем создать собственное расширение. Я решил не придумывать искусственных примеров и проиллюстрировать создание расширения с помощью обертки для какой-нибудь существующей C библиотеки. Посли долгих поисков(почти для всех популярных библиотек уже есть обертки) выбор пал на libdecodeqr, библиотеку для распознавания QR кодов. Простой пример для работы с ней в С можно посмотреть тут qrtest.c

Наше расширение будет очень простым, класс QRDecoder со статическим методом decode.
Метод decode в качестве аргумента берет имя файла с QR кодом, и возвращает строку там закодированную.
Пример использования (сравните с кодом в С:)):
    require "decodeqr"
    puts QRDecoder.decode "test.png"

Задача ясна, приступим к реализации.
Что нам потребуется:
1. пакет ruby1.8-dev для сборки расширений.
sudo apt-get install ruby1.8-dev
2. библиотека libdecodeqr.
sudo apt-get install libdecodeqr libdecodeqr-dev

Начнем с инициализации расширения:
    Init_decodeqr() {
        VALUE cQRDecoder = rb_define_class("QRDecoder", rb_cObject);
        rb_define_singleton_method(cQRDecoder, "decode", decode, 1);
    }

Все просто, создаем класс QRDecoder и статический метод decode.
Теперь реализация функции decode:
    VALUE decode(VALUE self, VALUE file) {
        char *file_name = StringValuePtr(file); //конвертируем Ruby строку в С
        //работа с библиотекой libdecodeqr, более подробно можно посмотреть в С примере
        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); //возвращаем Ruby строку
    }

Наше расширение готово! Код целиком можно посмотреть тут decodqr.c
Теперь нам нужно собрать наше расширение. Для этого мы используем стандартную Ruby библиотеку mkmf, с помощью которой мы сгенерируем Make файл.
Пример, файл extconf.rb:
    require 'mkmf'

    if have_library("cv") and #если есть библиотека "cv"
    have_library("decodeqr") and #и библиотека "decodeqr"
    have_library("highgui") and #а также "highgui"
    find_header("highgui.h", "/usr/include/opencv/") then # и нашли заголовочный файл "highgui.h"
        create_makefile("decodeqr") #тогда создаем Make файл
    else
        puts "oops..." #а иначе не создаем
    end

И результат его выполнения:
$ 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

А теперь стандартные:
    make
    sudo make install

и наше расширение готово к использованию.

Таким образом, если у вас есть С код, или хотите использовать С библиотеку, или вы хотите ускорить Ruby, вы можете просто сделать расширение и использовать его в своих Ruby программах.

Чего я не коснулся в этой статье:
1. Работа со строками и массивами
2. Сборщик мусора
3. Создание оберток для С структур

Об этом вы можете почитать в приведенных ссылках:
1. Файл README.EXT, который поставляеться с Ruby дистрибутивом.
2. Глава Extending Ruby в знаменитом pickaxe
3. Этот замечательный блог maxidoors.ru

Для заинтересовавшихся, библиотека libdecodeqr: trac.koka-in.org/libdecodeqr
Осторожно! Там все по японски:) Документацию можно посмотреть прямо в заголовочном файле:
trac.koka-in.org/libdecodeqr/browser/tags/release-0.9.3/src/libdecodeqr/decodeqr.h

В следующей части я расскажу про использование Ruby в качестве скриптового языка в C/C++ приложениях.
Tags:RubyC
Hubs: Ruby
Total votes 33: ↑28 and ↓5+23
Views5.2K

Popular right now