Ruby и C. Часть 3.

    В прошлых частях(часть 1,часть 2) мы рассмотрели использование С для ускорения или расширения Ruby. Сейчас же мы узнаем как использовать Ruby интерпретатор в программах, написанных на С/С++.
    В некоторых приложениях возникает необходимость во встроенном языке, для более тонкой настройки или для написания расширений без перекомпиляции. Ruby очень хорошо подходит для этой цели, т.к. имеет простое и удобное API для втраивания в C/C++ приложения. Например Google SketchUp использует Ruby в качестве скриптового языка.

    Давайте рассмотрим как встроить Ruby в наши С/С++ приложения.


    Начнем с рассмотрения функции rb_eval_string, которая выполняет строку с Ruby кодом.
    Давайте рассмотрим простой пример — програма для вычисления выражений. Для простоты у нас в выражениях будет только две переменные.
    Пример работы приложения, входные данные:
    	($a+$b)*$a-$b
    	2
    	3
    

    Результат:
    	($a+$b)*$a-$b = 7
    


    Посмотрим код:
    	#include <ruby.h>
    	#include <stdio.h>
    
    	int main() {
    		ruby_init(); //инициализация интерпретатора
    	
    		int a = 0;
    		int b = 0;
    		char expr[256];
    	
    		//читаем выражение и две переменные
    		scanf("%s", &expr); 
    		scanf("%d", &a); 
    		scanf("%d", &b);
    	
    		//создаем Ruby переменные
    		VALUE r_a = INT2NUM(a);
    		VALUE r_b = INT2NUM(b);
    		//и делаем их доступными в интерпретаторе
    		rb_define_variable("$a", &r_a);
    		rb_define_variable("$b", &r_b);
    	
    		//выполняем выражение и выводим результат
    		VALUE res = rb_eval_string(expr); 
    		printf("%s = %d\n", expr, NUM2INT(res));
    
    		return 0;
    	}
    

    Примечание: про тип данных VALUE, макросы INT2NUM и т.п., и более подробно про работу с Ruby C API смотрите в части 2

    В данном примере мы считали с клавиатуры две переменные и выражение с ними. Затем, на основе этих переменных, создали глобальные Ruby переменные и, с помощью метода rb_define_variable, сделали их доступными в интерпретаторе.
    В итоге, с помощью метода rb_eval_string, мы выполнили выражение и вывели его результат на экран.

    Теперь перейдем к загрузке и выполнению скриптов на Ruby.
    Рассмотрим такой пример: нам нужно получать хэш строки по пользовательскому алгоритму. Сценарий работы такой, пользователь создает файл alg.rb и реализует в нем алгоритм хэширования. В нашей програме мы загружаем пользовательский скрипт, передаем ему строку для хэширования, выполняем скрипт и печатаем результат.
    Посмотрим код:
    	#include <ruby.h>
    	#include <stdio.h>
    
    	int main() {
    		ruby_init(); //инициализация интерпретатора
    		ruby_init_loadpath(); //возможность подключать стандартные библиотеки, например require 'MD5'
    		VALUE res = rb_str_new2("some test string"); //наша тестовая строка
    		rb_define_variable("$res", &res); //делаем ее доступной скрипту
    		rb_load_file("alg.rb"); //загружаем файл с алгоритмом
    		//выполняем скрипт и выводим результат
    		ruby_exec(); 
    		printf("%s\n", StringValuePtr(res));
    		return 0;
    	}
    


    Теперь в файле alg.rb мы можем реализовывать какие угодно алгоритмы хэширования.
    Например, к количество пробелов в строке(не очень удачный хэш:)):
    	hash = 0
    	$res.split(//).each {|c| hash += 1 if c == " "}
    	$res = hash.to_s
    

    или сумма всех символов в шестнадцатеричном виде:
    	hash = 0
    	$res.split(//).each {|c| hash += c[0]}
    	$res = hash.to_s(16)
    

    или же md5, с помощью стандартной библиотеки:
    	require "md5"
    	$res = MD5.hexdigest($res)
    


    Теперь о компиляции приложений со встроенным Ruby, на пример gcc:
    gcc test.c -o test -I<папка с ruby.h> -lruby
    либо:
    gcc test.c -o test -I<папка с ruby.h> -lruby1.8

    Для компиляции в *nix системах нужно поставить пакет ruby-dev, например так:
    sudo apt-get install ruby1.8-dev
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 11
    • +1
      Если не секрет, какие накладные расходы на вызов? Например для первого простого примера — сколько раз в секунду на вашем железе дернуть можно?
      • 0
        Немного не понял какие именно накладные расходы, на инициализацию интерпретатора или на вызов rb_string_eval?
        Для второго прогнал такой тестик(первый пример миллион раз и без ввода/вывода):
        pastie.org/368765
        Результаты:
        $ sysctl -a | grep brand_string
        machdep.cpu.brand_string: Intel® Core(TM)2 Duo CPU     T7500  @ 2.20GHz
        
        $ time ./calc
        real	0m4.193s
        user	0m4.096s
        sys	0m0.022s
        
        • +1
          Спасибо, то что нужно. Значит 250 тыс. вызовов в секунду.
      • 0
        имеет простое и удобное API

        «API» в русском языке мужского рода.
        • 0
          Дайте ссылку на правило, исправлю. При переводе ППИ(Прикладной програмный интерфейс) — согласен. А вот при латинском написании мужской и средний род используется примерно одинаково.
          • 0
            Не веришь?

            На, почитай: eraznaniy.eraz.msk.ru/edu/openlesson/course4/lesson34/
            • 0
              Почитал, спасибо.
              «Аббревиатуры, оканчивающиеся на гласный звук, не склоняются и преимущественно относятся к среднему роду»
              • 0
                Слово «Интерфейс» в аббревиатуре «API» мужского рода. ;)
        • 0
          спасибо за статью.
          • 0
            Захотел встроить ruby в проект. Взял ruby 1.9.1 и первый пример из статьи. Вот только проблема: если ввести первой строкой $a+$b+$c (переменная $c не определялась ранее) — под windows — access violation на адрес 0x00000000, debian — segmentation fault… Как-то странно движок реагирует… Нужна какая-то особая инициализация движка для обработки ошибок?
            • 0
              Ну движок вполне так себе обычно реагирует, так же как и stand-alone интерпретатор.
              То есть, если встречает ошибку — печатает информацию о ней в stderr и вызывает exit с не нулевым кодом.
              Но, а так как интерпретатор-то мы вызываем извне, то когда он выходит, запускающее приложение к этому не готово и всякие сегфолты и вылазят:)
              Для обработки ошибок есть функция rb_protect. Ей передается функция-обертка, содержащая сишный эквивалент Ruby кода(rb_* функции). Последним аргументом передается число error.
              Дальше механизм обработки ошибок такой:
              — если error не нулевое, значит у нас ошибка, надо бы ее показать:)
              — получаем глобальную переменную $!, содержащию информацию о последнем эксепшене: rb_gv_get("$!")
              — конвертируем полученное значение сначала в Ruby строку, затем в сишную строку и выводим либо на экран, либо в лог, либо еще куда.

              С примерами можно тут посмотреть(статья хоть и старая, но все еще актуальная):
              metaeditor.sourceforge.net/embed/

              А для интерпретации строк есть еще более простой способ, функция rb_eval_string_protect, которая вторым аргументом берет тот же error, что и rb_protect. И дальше, если error не нулевая — описанная выше последовательность действий.
              Вот первый пример из статьи с обработкой ошибок во входной строке: pastie.org/1063030

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое