Ответ 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
одним из "примитивных".