В прошлый раз мы кратко познакомились с основными структурами интерпретатора ruby. Сегодня я попробую углубиться в интересующую нас тему. Помимо этого, я буду приводить некоторые разъясняющие примеры, как на языке си, так и на руби.
Как лучше всего практиковать изучения ядра Ruby
Теория это хорошо, но честно говоря лучше практиковать и ошибаться :) По крайней мере в этом деле.
Я расскажу вам о своем workflow:
MacOSX-10.5.8 + Xcode + текстовый редактор + исходники ruby 1.8.7 + установленный по-умолчанию ruby.
1) Подготавливаем файлы
rbdev$ cd path/to/ruby/source rbdev$ mkdir x rbdev$ cd x rbdev$ touch my_ext.c rbdev$ touch extconf.rb
2) Редактируем extconf.rb
Самый простой вариант того, как должен выглядеть этот файл:
require 'mkmf' create_makefile("my_ext")
3) Открываем файл нашего расширения (my_ext.c) и заполняем:
#include <stdlib.h> #include <ruby.h> #include <stdio.h> void Init_My_Ext(void) { // инициализация нашего расширения происходит здесь // создадим константу VALUE global_const_value = rb_str_new2((char*)"This is my global CONST"); rb_define_global_const((char*)"MyGlobalConst", global_const_value); }
4) Компилируем (с правами рута, например)
bash-3.2# ruby extconf.rb creating Makefile bash-3.2# make gcc -I. -I/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/universal-darwin9.0 -I/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/universal-darwin9.0 -I. -fno-common -arch ppc -arch i386 -Os -pipe -fno-common -c my_ext.c cc -arch ppc -arch i386 -pipe -bundle -undefined dynamic_lookup -o my_ext.bundle my_ext.o -L. -L/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib -L. -arch ppc -arch i386 -lruby -lpthread -ldl -lm bash-3.2# make install /usr/bin/install -c -m 0755 my_ext.bundle /Library/Ruby/Site/1.8/universal-darwin9.0
5) Проверяем наше скомпилированное расширения
bash-3.2# irb >> require "My_Ext" => true >> MyGlobalConst => "This is my global CONST"
Как видите мы успешно создали наше расширение, давайте посмотрим еще один вариант «вскрытия внутренностей» (my_ext.c)
void Init_My_Ext(void) { char* my_code = (char*)"puts 100, "Welcome to debugger""; rb_eval_string(my_code); printf("%sn", (char*) "We can output to irb directly from c-code :D"); }
Irb вывыдет три строки, как и ожидалось:
>> require "My_Ext" 100 Welcome to debugger We can output to irb directly from c-code :D
Так же легко это делается на системах linux. (Сколько бы я не пытался заставить компилироваться расширения руби на Windows – у меня ничего не получилось)
Освежим память
Весь наш с тобою разговор, читатель, будет проходить вокруг уже знакомых тебе структур, составляющих основу интерпретатора руби.
Давай освежим память и взглянем на них:
struct RBasic { unsigned long flags; VALUE klass; }; struct RObject { struct RBasic basic; struct st_table *iv_tbl; }; struct RClass { struct RBasic basic; struct st_table *iv_tbl; struct st_table *m_tbl; VALUE super; };
Как ты уже понял, структуруа RBasic находится внутри структур RClass и RObject.
Я не хочу запутать вас, и попробую привести пример «вложенности» используя руби:
RBasic = Struct.new(:klass) RObject = Struct.new(:r_basic) r_basic = RBasic.new(Class) r_object = RObject.new(r_basic) puts r_object.r_basic.klass # => Class
Из вышеописанного кода понятно, что мы создаем две структуры – RBasic и RObject. RBasic содержит в себе «указатель» на класс. Структура RObject содержит в себе структуру RBasic, которая в свою очередь содержит в себе указатель на класс.
Давайте я легко заменю класс, содержащийся внутри RBasic:
r_basic = RBasic.new(MyCustomClass) # ... puts r_object.r_basic.klass # => MyCustomClass
unsigned long VALUE
И так, обрати внимание на строку «VALUE klass» структуры интерпретатора руби(!) struct RBasic.
Если ты читал первую часть моего рассказа, то понял, что это ничто иное как указатель на класс. Но тип переменной VALUE (typedef unsigned long VALUE) используется не только для этого.
VALUE может указывать и на структуры других типов, например RString или RFile.
Как это происходит? Дело в том, что VALUE определен как unsigned long, поэтому мы легко можем хранить внутри этой переменной адрес другой структуры. Для этого нам придётся воспользоваться type-cast’ингом.
Вот тебе небольшой пример того, как можно «кастить»:
#include <stdlib.h> #include <stdio.h> struct class { char* name; }; typedef unsigned long VALUE; typedef struct class Class; int main(void) { // Пример номер один: // ptr объявлен как обычный int, и он будет содержать в себе адрес переменной a, // мы вручную “кастим” адрес переменной a к типу int: int a = 10; int ptr = (int)&a; // сначала кастим наш int в указатель на int (int*), а потом достаем значение по адресу printf("%in", *((int*)ptr) ); // выведет 10 // Пример номер два: VALUE object; Class my; object = (VALUE)&my; ((Class*)object) -> name = (char*)"MySuperClass"; printf("%sn", ((Class*)object) -> name ); // выведет "MySuperClass" }
Но нам не придется мучиться и писать откровенно страшный и непонятный код, создатели руби заранее придумали макросы, для облегчения повседневных задач.
Давайте рассмотрим пример с объектом-строкой:
// макрос #define RSTRING(obj) (R_CAST(RString)(obj))
Взглянем на структуру RString:
struct RString { struct RBasic basic; long len; char *ptr; union { long capa; VALUE shared; } aux; };
Существуют специальные функции, которые создают объекты разных классов. Например, функция rb_str_new2 создает объект класса String и возвращает указатель на созданный объект:
void Init_My_Ext(void) { // создаем простую строку char* simple_string = (char*)"My String"; // создаем строку в руби и присваиваем адрес нового объекта переменной my_string VALUE my_string = rb_str_new2(simple_string); // нам не приходится вручную писать type-кастинги, мы просто воспользуемся макросом RSTRING printf("%in", RSTRING(my_string) -> len); // выведет длинну строки => 9 /* так как структура RString содержит в себе указатель на строку (в нашем случае на simple_string) мы легко можем воспользоваться этим %) */ printf("%sn",RSTRING(my_string) -> ptr); // выведет "My String" }
Как видите макросы облегчают жизнь, для большего осознания этого приведу пример не-макросного кода:
void Init_My_Ext(void) { char* my_c_str = (char*)"MyString"; VALUE my_rb_str1 = rb_str_new2(my_c_str); VALUE my_rb_str2 = rb_str_new2( ((struct RString*)my_rb_str1) -> ptr ); printf("%sn", (char*)((struct RString*)my_rb_str1) -> ptr); }
Когда я впервые увидел ruby.h я был полностью шокирован. Теперь я понимаю, что макросы облегчают жизнь, и конечно же ими нужно не пренебрегать, а аккуратно изучать и использовать.
Подведем итог:
VALUE создан для того, чтобы ссылаться на различные R***** структуры. Но это далеко не все,что можно рассказать о VALUE. Я вернусь к этому чуточку позже.
flags
Помимо класса, структура RBasic содержит строку «unsigned long flags». Вы наверно спросите — «Почему не заменять unsigned long flags на VALUE flags?». Дело в том, что создатели руби заранее определились, что будут использовать VALUE только как указатель на структуры R******. К тому же во многих макросах используется определение VALUE, что может вызвать ошибку, если мы подсунем указатель НЕ НА СТРУКТУРУ (а мы все с вами знаем, что сделать это как два пальца ******ть). Думаю в этом нет ничего сложного, немного поработав с исходниками руби и написав несколько расширений вы лучше поймёте о чем идёт речь.
Во-первых, flags содержит в себе тип структуры. (смотрим в наш любимый ruby.h и находим там определения типов структур, например: T_NIL, T_OBJECT, T_STRING, T_CLASS и т.д.)
Во-вторых, flags содержит в себе специальные флаги для интерпрнтатора ruby, о которых я возможно когда-нибудь расскажу (если конечно меня вообще заинтересуют лезть так глубоко)
Попробуем немного поработать с флагами:
void Init_My_Ext(void) { // создадим новую строку VALUE my_str = rb_str_new2((char*)"Rails rocks!"); if(TYPE(my_str) == T_STRING) printf("Yeah, this is stringn"); if(TYPE(my_str) == T_OBJECT) printf("OBJECT?n"); }
Выведет только «Yeah, this is string». Так же мы можем воспользоваться макросом CLASS_OF:
void Init_My_Ext(void) { VALUE my_str = rb_str_new2((char*)"Rails rocks!"); if(CLASS_OF(my_str) == rb_cString) printf("yeah, this is string-classn"); if(CLASS_OF(my_str) == rb_cObject) printf("yeah, this is object-classn"); }
Выведет только «yeah, this is string-class».
Посмотрим на определение макроса CLASS_OF:
#define CLASS_OF(v) rb_class_of((VALUE)(v))затем смотрим в определение функции rb_class_of:
static inline VALUE #if defined(HAVE_PROTOTYPES) rb_class_of(VALUE obj) #else rb_class_of(obj) VALUE obj; #endif { if (FIXNUM_P(obj)) return rb_cFixnum; if (obj == Qnil) return rb_cNilClass; if (obj == Qfalse) return rb_cFalseClass; if (obj == Qtrue) return rb_cTrueClass; if (SYMBOL_P(obj)) return rb_cSymbol; return RBASIC(obj)->klass; }
Во-первых нас интересует последняя строка -> «return RBASIC(obj) -> klass».
Мы уже знаем, что сама по себе структуру RString не содержит никакой информации о своем классе, но она содежит в себе структуру RBasic, которая в свою очередь содержит указатель на класс объекта. Таким образом строка «return RBASIC(obj) -> klass» обращается к структуре basic, находящейся внутри obj и возвращает класс объекта, например rb_cString.
Но почему перед этой строкой стоят какие-то странные условия? А дело все в том, что не каждая структура руби содержит в себе структуру RBasic.
Несмотря на то, что в руби все – объект, классы Symbol, Fixnum, NilClass, FalseClass, TrueClass не содержат внутри себя структуры RBasic. Это сделано для того, чтобы ускорить интерпритатор. Только подумайте, чтобы случилось, если бы на каждое новое число создавалась целая структура.
Небольшой пример на руби:
puts 1.object_id # => 3 puts 1.object_id # => 3 puts :text.object_id # => 109858 puts :text.object_id # => 109858 puts "Hello, habr".object_id # => 33970 puts "Hello, habr".object_id # => 33950 (!!!)
Сколько бы мы не пытались узнать id объекта Fixnum или Symbol -> мы получаем одно и тоже значение. А вот со строкой все иначе, она создалась два раза. Именно поэтому выгоднее использовать символы, вместо строк, особенно это касается разработчиков ruby on rails ( ну или любого другого софта на руби ):
map.root :controller => :main, :action => :index # отлично map.root :controller => "main", :action => "index" # не очень хорошо
Что дальше?
В следующей статье я познакомлю вас с ID, более подробно расскажу о работе с числами и объектами класса Symbol. Конечно же весь мой рассказ будет сопровождаться поясняющими примерами на руби и си.

{ 23 comments… read them below or add one }
Хороший материал! Спасибо.
(только из бросающегося в глаза: интерпрЕтатор пишется через «е»)
Спасибо тебе =)
Ошибку обязательно исправлю. Буду продолжать писать на эту тему.
Может быть ты хочешь узнать что-то более подробно?
Дизайн SummerDesign выглядит просто чудесно.
И еще маленький стилистический нюанс:
«…Как ты уже понял, структуруа RBasic находится внутри структур RClass и RObject.
Я не хочу запутать вас, и попробую…»
– автор был только что на ты, а потом опять на «вы» переходит… :)
В целом полезные статьи, если захочется написать свое расширение, здорово поможет :)
Да, я опосался, что наверняка попутаю где-нибудь ‘ты и вы’. Мне не очень нравиться обращение на «вы». Но это «вы» уже давно въелось мне в мозги %)
Побились инклюды.
Кажется починил.
Какой же ужас эти парсеры, все вроде отлично, проверено по два раза, а все равно после публикации 2-3 ошибки возникают =)
> flags содержит в себе специальные флаги для компилятора
о чем это Вы?
о gcc? как содержимое переменной может содержать флаги для компиляции? код то уже скомпилирован
может о самом руби? но 1.8.7 не содержит и намека на компиляцию
oops %)
Вот это косяк, спасибо вам. Заработался и написал лишнего :)
c/компилятор/интрепретатор
Вот тебе небольшой пример того, как можно «кастить»:
2 инклюда в коде потеряли
Спасибо, исправил, и потерялись почему-то инклюды не только в этом месте.
В почему бы не записывать скринкасты и делать больше упор на RoR? Было бы интересно, ну со скринкастами можно и обойтись))
Ну может скоро дойдем и до ROR ;) Правда я не понимаю, почему рор и скринкасты лучше руби и его ядра?
Я думаю с глубоким знанием рор можно заработать хорошие деньги.
А с глубоким знанием ядра руби можно отрастить бороду, но все таки продолжать слать своё резюме. )) Мэйнстрим такой
Крайности какие-то %)
У меня весит несколько идей для рор-постов. Так или иначе, с рор я работаю, с руби я еще и развлекаюсь.
О боже мой!
Пожалуйста, пишите про руби без рельсов.
Я просто в недоумении насколько сильно руби отождествляют с рельсами.
Вроде же хороший язык, а почти никто и нигде не использует, если не смотреть на рельсы.
Это как-то печалит((
Зная недры устройства руби, вам вообще никакого труда не составит выучить рельсы.
И если для вас главное деньги зарабатывать то почему вы не пошли по пути java, php или .NET? Было бы Вам зарабатывание денег на каждом углу. Всё -таки посмотрев на вакансии на каких-нибудь сайтах можно заметить что это самые распространённые языки. Поймите что не для всех первоочередная задача – получение денег. Я вот выбрал себе путь программиста не потому что здесь денег много, а потому что мне это нравится, если бы деньги были нужны, можно было бы найти профессию получше. И языками/технологиями учу я занимаюсь не теми которые денежные, а теми которые в первую очередь мне нравятся.
согласен =) Я тоже выбрал эту специальность, потому что мне она по душе, и выбрал руби, потому что с ним вообще жизнь хороша и приятна
По ROR уже есть столько скринкастов, что хоть ешь их. Лучшие на мой вкус http://railscasts.com/, даже кто-то их на русский язык переводил. Так что зачем лезть в переполненную нишу?
+1 %)
к тому же мне такой вид подачи небольшого количества информации совсем не нравиться.
Зачем компилить от рута? В макоси прекрасно работает sudo, при необходимости ротового доступа.
Да пиши ты уже «обоссать» вместо «******ть» или выбирай цензурный синоним, так по глазам режут эти звездочки.
Привет, обдумаю это.
мать и ругань прекрасна, пис. %)
Дык да, о рельсах если и буду писать – то опять же о их ядре, да и до него надо добраться =)
{ 1 trackback }