Вопросы по API расширений Ruby C - PullRequest
16 голосов
/ 13 августа 2011

Итак, недавно у меня возникла печальная необходимость сделать расширение C для Ruby (из-за производительности). Так как у меня были проблемы с пониманием VALUE (и до сих пор есть), поэтому я посмотрел на источник Ruby и обнаружил: typedef unsigned long VALUE; ( Ссылка на источник , но вы заметите, что есть несколько других 'способы' это сделано, но я думаю, что это по сути long; поправьте меня, если я ошибаюсь). Итак, при изучении этого вопроса я обнаружил интересное сообщение в блоге , в котором говорится:

"... в некоторых случаях объект VALUE мог бы БЫТЬ данными вместо того, чтобы указывать на данные."

Что меня смущает, так это то, что когда я пытаюсь передать строку в C из Ruby, и использую RSTRING_PTR(); в VALUE (переданном в C-функцию из Ruby), и пытаюсь «отладить» ее с помощью strlen(); возвращает 4. Всегда 4.

пример кода:

VALUE test(VALUE inp) {
    unsigned char* c = RSTRING_PTR(inp);
    //return rb_str_new2(c); //this returns some random gibberish
    return INT2FIX(strlen(c));
}

Этот пример всегда возвращает 1 как длину строки:

VALUE test(VALUE inp) {
    unsigned char* c = (unsigned char*) inp;
    //return rb_str_new2(c); // Always "\x03" in Ruby.
    return INT2FIX(strlen(c));
}

Иногда в ruby ​​я вижу исключение, говорящее «Не могу преобразовать модуль в строку» (или что-то в этом роде, , однако я так много возился с кодом, пытаясь выяснить это, что не могу воспроизвести ошибка теперь ошибка произошла бы, когда я попытался StringValuePtr(); [Мне немного непонятно, что именно это делает. Документация говорит, что он изменяет переданный параметр на char*] в inp):

VALUE test(VALUE inp) {
    StringValuePtr(inp);
    return rb_str_new2((char*)inp); //Without the cast, I would get compiler warnings
} 

Итак, рассматриваемый код Ruby: MyMod::test("blahblablah")

РЕДАКТИРОВАТЬ : исправлено несколько опечаток и немного обновлено сообщение.


Вопросы

  1. Что именно держит VALUE imp? Указатель на объект / значение? Само значение?
  2. Если оно содержит само значение: когда оно это делает, и есть ли способ проверить его?
  3. Как мне получить доступ к значению (поскольку мне кажется, что я получаю доступ почти ко всему , но значение)?

P.S .: Мое понимание C на самом деле не самое лучшее, но это работа в процессе; также прочитайте комментарии в фрагментах кода для дополнительного описания (если это поможет).

Спасибо!

Ответы [ 2 ]

29 голосов
/ 13 августа 2011

Ruby Strings против C строк

Давайте начнем со строк.Прежде всего, прежде чем пытаться получить строку в C, полезно сначала вызвать StringValue(obj) на вашем VALUE.Это гарантирует, что вы действительно будете иметь дело со строкой Ruby в конце, потому что, если она еще не является строкой, она превратит ее в одну, вызвав ее вызовом метода to_str этого объекта.Таким образом, это делает вещи более безопасными и предотвращает случайные segfault, которые вы могли бы получить в противном случае.

Следующее, на что нужно обратить внимание, это то, что строки Ruby не определены \0, так как ваш код C будет ожидать, что они сделают такие вещи, какstrlen и т. Д. Работают должным образом.Вместо этого строки Ruby несут с собой информацию о своей длине - поэтому в дополнение к RSTRING_PTR(str) есть также макрос RSTRING_LEN(str) для определения фактической длины.

Итак, что теперь делает StringValuePtr, возвращает не-char * с нулевым символом в конце - это отлично подходит для буферов, где у вас есть отдельная длина, но не то, что вы хотите, например, strlen.Вместо этого используйте StringValueCStr, и строка изменится на ноль, так что это будет безопасно для использования с функциями в C, которые ожидают, что она будет заканчиваться нулем.Но старайтесь избегать этого везде, где это возможно, потому что эта модификация гораздо менее производительна, чем извлечение строки с ненулевым символом в конце, которую не нужно изменять вообще.Удивительно, если вы следите за тем, как редко вам действительно понадобятся «настоящие» строки C.

self как неявный аргумент VALUE

Еще одна причина, по которой ваш текущийкод не работает должным образом, так как каждая функция C, вызываемая Ruby, передается self как неявная VALUE.

  • Никаких аргументов в Ruby (например, obj.doit) не переводится в

    VALUE doit (VALUE self)

  • Фиксированное количество аргументов (> 0, например, obj.doit (a, b)) преобразуется в

    VALUE doit (VALUE self, VALUE a, VALUE b)

  • Var args в Ruby (например, obj.doit (a, b = nil)) переводится в

    VALUE doit (int argc, VALUE * argv, VALUE self)

в рубине.Итак, в вашем примере вы работали с , а не строкой, переданной вам Ruby, но на самом деле с текущим значением self, то есть объектом, который был получателем, когда вы вызывали эту функцию.Правильное определение для вашего примера было бы

static VALUE test(VALUE self, VALUE input) 

Я сделал это static, чтобы указать другое правило, которому вы должны следовать в своих расширениях Си.Делайте ваши функции C общедоступными, только если вы собираетесь делиться ими между несколькими исходными файлами.Поскольку это почти никогда не относится к функциям, которые вы присоединяете к классу Ruby, вы должны объявить их как static по умолчанию и сделать их общедоступными только в том случае, если для этого есть веские основания.

Что такое VALUE и откуда оно взято?

Теперь самое сложное.Если вы углубитесь во внутренности Ruby, то в gc.c. вы найдете функцию rb_objnew .Здесь вы можете видеть, что любой вновь созданный объект Ruby становится VALUE, будучи приведенным как один из чего-то, называемого freelist.Он определяется как:

#define freelist objspace->heap.freelist

Вы можете представить objspace как огромную карту, в которой хранится каждый объект, который в данный момент жив в данный момент времени, в вашем коде.Это также место, где сборщик мусора выполняет свои обязанности, и, в частности, структура heap - это место, где рождаются новые объекты.«Фрилист» кучи снова объявляется RVALUE *.Это C-внутреннее представление встроенных типов Ruby.RVALUE фактически определяется следующим образом:

typedef struct RVALUE {
    union {
    struct {
        VALUE flags;        /* always 0 for freed obj */
        struct RVALUE *next;
    } free;
    struct RBasic  basic;
    struct RObject object;
    struct RClass  klass;
    struct RFloat  flonum;
    struct RString string;
    struct RArray  array;
    struct RRegexp regexp;
    struct RHash   hash;
    struct RData   data;
    struct RTypedData   typeddata;
    struct RStruct rstruct;
    struct RBignum bignum;
    struct RFile   file;
    struct RNode   node;
    struct RMatch  match;
    struct RRational rational;
    struct RComplex complex;
    } as;
    #ifdef GC_DEBUG
    const char *file;
    int   line;
    #endif
} RVALUE;

То есть, по сути, объединение основных типов данных, о которых знает Ruby.Что-то пропустили?Да, Fixnums, Symbols, nil и логические значения там не включены.Это потому, что эти виды объектов представлены непосредственно с помощью unsigned long, к которому VALUE сводится в конце.Я думаю, что при проектировании было принято решение (помимо того, что это крутая идея), что разыменование указателя может быть немного менее производительным, чем сдвиги битов, которые необходимы в настоящее время при преобразовании VALUE в то, что он фактически представляет.По сути,

obj = (VALUE)freelist;

говорит, дайте мне все, что указывает фриланлист на данный момент, и относитесь к unsigned long.Это безопасно, потому что freelist является указателем на RVALUE - и указатель также можно безопасно интерпретировать как unsigned long.Это означает, что каждый VALUE, за исключением тех, которые несут Fixnums, символы, ноль или логические значения, по сути являются указателями на RVALUE, остальные непосредственно представлены в VALUE.

Ваш последний вопрос, как вы можетепроверить, что означает VALUE?Вы можете использовать макрос TYPE(x), чтобы проверить, будет ли тип VALUE одним из «примитивных».

5 голосов
/ 13 августа 2011
VALUE test(VALUE inp)

Первая проблема здесь: inp is self (так что, в вашем случае, модуль). Если вы хотите сослаться на первый аргумент, вам нужно добавить собственный аргумент перед этим (что заставляет меня добавить -Wno-unused-parameters к моим cflags, поскольку это никогда не используется в случае функций модуля):

VALUE test(VALUE self, VALUE inp)

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

VALUE - это ссылка на объект Ruby, но не прямой указатель на то, что он может содержать (например, char * в случае строки). Вам нужно получить этот указатель, используя несколько макросов или функций в зависимости от каждого объекта. Для строки вы хотите, чтобы StringValuePtr (или StringValueCStr гарантировал, что строка оканчивается нулем), которая возвращает указатель (он не меняет содержимое вашего ЗНАЧЕНИЕ в любом случае).

strlen(StringValuePtr(thing));
RSTRING_LEN(thing); /* I assume strlen was just an example ;) */

Фактическое содержание VALUE - это, по крайней мере, в МРТ и YARV, object_id объекта (или, по крайней мере, это после сдвига битов).

Для ваших собственных объектов VALUE, скорее всего, будет содержать указатель на объект C, который вы можете получить, используя Data_Get_Struct:

 my_type *thing = NULL;
 Data_Get_Struct(rb_thing, my_type, thing);
...