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