Подтвердить что ты не робот

Вопросы API-интерфейсов Ruby C

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

"... в некоторых случаях объект VALUE мог бы быть данными вместо POINTING TO data."

Что меня смущает, когда я пытаюсь передать строку на 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")

EDIT. Исправлено несколько опечаток и немного обновлено сообщение.


Вопросы

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

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

Спасибо!

4b9b3361

Ответ 1

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

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

Следующее, на что нужно обратить внимание, это то, что строки Ruby не являются \0 -terminated, так как ваш код 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)

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

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

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

static VALUE test(VALUE self, VALUE input) 

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

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

Теперь к более сложной части. Если вы глубоко вникнете в внутренности Ruby, то вы найдете функцию rb_objnew в gc.c. Здесь вы можете увидеть, что любой вновь созданный объект 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, что в конце концов заканчивается a VALUE. Я думаю, что конструктивное решение было (помимо того, что это крутая идея), что разыменование указателя может быть немного менее эффективным, чем сдвиги бит, которые в настоящее время необходимы при преобразовании VALUE в то, что он фактически представляет. По существу,

obj = (VALUE)freelist;

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

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

Ответ 2

VALUE test(VALUE inp)

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

VALUE test(VALUE self, VALUE inp)

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

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

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

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

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

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