В прошлой части мы рассмотрели библиотеку RubyInline, которая позваляет делать вставки C кода прямо в Ruby методы. Ее очень удобно использовать если нам нужно ускорить несколько критичных методов. Но если нам нужно реализовать C библиотеку и использовать в Ruby, или сделать обертку для уже существующей, то нам на помощь придет C API для создания расширений.
Ruby C API предоставляет нам средства написания Ruby кода с помощью C. Звучит немного странно, но давайте рассмотримм пример.
Ruby код:
Аналогичный C код:
Тривиальный пример — создание класса с одним методом. Теперь рассмотрим подробнее C код.
С помощью функции rb_define_class мы создаем класс Test наследник класса Object. Затем с помощью функции rb_define_method мы создаем метод test класса Test, и в качестве реализации этого метода передаем функцию test_method. Последний параметр означает что наш метод не имеет аргументов.
В примере везде используется тип данных VALUE. Любой Ruby объект, будь то строка, число, класс, модуль и т.п., в C имеет тип VALUE.
Ruby C API предоставляет набор макросов и функции для конвертации типов данных Ruby в C и наоборот.
Вот некоторые из них(из Ruby в С):
"
Из С в Ruby:
Модули создаются аналогично классам:
Теперь о методах.
Есть несколько функций для создания методов. Вот некоторые из них:
Функция с реализацией метода должна быть следующего вида:
self, это указатель на объект, он присутствует всегда, даже если у метода нет аргументов.
В зависимости от значения аргумента argc, сигнатура может меняться. Отрицательные значения argc используются для определения метода с переменным числом аргументов.
Если argc равен -1:
argc — количество аргументов, *argv — массив аргументов.
Если argc равен -1:
args — Ruby массив с аргументами.
Функция Init_test() вызывается при инициализации нашего расширения, в ней мы как раз создаем классы и модули.
Теперь, после рассмотрения основных возможностей Ruby C API, давайте попробуем создать собственное расширение. Я решил не придумывать искусственных примеров и проиллюстрировать создание расширения с помощью обертки для какой-нибудь существующей C библиотеки. Посли долгих поисков(почти для всех популярных библиотек уже есть обертки) выбор пал на libdecodeqr, библиотеку для распознавания QR кодов. Простой пример для работы с ней в С можно посмотреть тут qrtest.c
Наше расширение будет очень простым, класс QRDecoder со статическим методом decode.
Метод decode в качестве аргумента берет имя файла с QR кодом, и возвращает строку там закодированную.
Пример использования (сравните с кодом в С:)):
Задача ясна, приступим к реализации.
Что нам потребуется:
1. пакет ruby1.8-dev для сборки расширений.
2. библиотека libdecodeqr.
Начнем с инициализации расширения:
Все просто, создаем класс QRDecoder и статический метод decode.
Теперь реализация функции decode:
Наше расширение готово! Код целиком можно посмотреть тут decodqr.c
Теперь нам нужно собрать наше расширение. Для этого мы используем стандартную Ruby библиотеку mkmf, с помощью которой мы сгенерируем Make файл.
Пример, файл extconf.rb:
И результат его выполнения:
А теперь стандартные:
и наше расширение готово к использованию.
Таким образом, если у вас есть С код, или хотите использовать С библиотеку, или вы хотите ускорить 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++ приложениях.
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++ приложениях.