16

所以,最近我不幸需要为 Ruby 做一个 C 扩展(因为性能)。由于我在理解方面遇到了问题VALUE(并且仍然存在),所以我查看了 Ruby 源代码并发现:(typedef unsigned long VALUE;Link to Source,但您会注意到还有其他一些“方法”可以完成,但我认为它本质上是一个long; 如我错了请纠正我)。因此,在进一步调查时,我发现了一篇有趣的博客文章,其中说:

“...在某些情况下,VALUE 对象可能是数据,而不是指向数据。”

让我感到困惑的是,当我尝试将字符串从 Ruby 传递给 C 并使用(从 RubyRSTRING_PTR();传递VALUE给 C 函数),并尝试用它“调试”它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*] on 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. 我如何实际访问该值(因为我似乎访问了 该值之外的几乎所有内容)?

PS:我对 C 的理解并不是最好的,但它还在进行中;此外,请阅读代码片段中的注释以获取更多描述(如果有帮助)。

谢谢!

4

2 回答 2

30

Ruby 字符串与 C 字符串

让我们先从字符串开始。首先,在尝试在 C 中检索字符串之前,最好先调用StringValue(obj)您的字符串VALUE。这确保您最终将真正处理一个 Ruby 字符串,因为如果它还不是一个字符串,那么它将通过调用该对象的to_str方法来强制它变成一个字符串。因此,这使事情变得更安全,并防止偶尔出现段错误。

接下来要注意的是 Ruby 字符串不会\0终止,因为您的 C 代码会期望它们使诸如strlenetc. 之类的事情按预期工作。Ruby 的字符串会携带它们的长度信息——这就是为什么除了RSTRING_PTR(str)RSTRING_LEN(str)来确定实际长度的原因。

所以StringValuePtr现在要做的是将非零终止的返回char *给你 - 这对于你有单独长度的缓冲区非常有用,但不是你想要的,例如strlen. StringValueCStr相反,它会将字符串修改为以零结尾,以便与 C 中期望它以零结尾的函数一起使用是安全的。但是,尽可能避免这种情况,因为这种修改比检索根本不需要修改的非零终止字符串的性能要低得多。如果您密切关注这一点,您实际上很少需要“真正的”C 字符串,这会令人惊讶。

self 作为隐式 VALUE 参数

当前代码无法按预期工作的另一个原因是 Ruby 调用的每个 C 函数都self作为隐式传递VALUE

  • Ruby 中没有参数(例如 obj.doit )转换为

    价值 doit(价值自我)

  • 固定数量的参数(>0,例如 obj.doit(a, b))转换为

    价值doit(价值自我,价值a,价值b)

  • Ruby 中的 Var args(例如 obj.doit(a, b=nil))转换为

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

在红宝石。因此,您在示例中处理的不是Ruby 传递给您的字符串,而是 的当前值self,即调用该函数时作为接收者的对象。您的示例的正确定义是

static VALUE test(VALUE self, VALUE input) 

我这样做是static为了指出您应该在 C 扩展中遵循的另一条规则。仅当您打算在多个源文件之间共享 C 函数时才公开它们。由于附加到 Ruby 类的函数几乎不会出现这种情况,因此您应该将它们声明为static默认值,并且只有在有充分理由时才将它们公开。

什么是价值,它来自哪里?

现在到更难的部分。如果您深入研究 Ruby 内部,您会在 gc.c 中找到函数rb_objnew。在这里,您可以看到任何新创建的 Ruby 对象都VALUE通过从名为freelist. 它定义为:

#define freelist objspace->heap.freelist

您可以将其想象objspace为一个巨大的地图,其中存储了代码中给定时间点当前处于活动状态的每个对象。这也是垃圾收集器履行职责heap的地方,特别是结构是新对象诞生的地方。堆的“freelist”再次声明为RVALUE *. 这是 Ruby 内置类型的 C 内部表示。AnRVALUE实际上定义如下:

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、符号nil和布尔值不包括在内。这是因为这些类型的对象是直接使用unsigned longaVALUE归结为最终表示的。我认为那里的设计决策(除了是一个很酷的想法之外)取消引用指针的性能可能比当前在将其转换VALUE为实际表示的内容时所需的位移略低。本质上

obj = (VALUE)freelist;

说给我任何 freelist 当前指向的内容,然后将其视为unsigned long. 这是安全的,因为 freelist 是指向RVALUE- 的指针,并且指针也可以安全地解释为unsigned long. 这意味着VALUE除了带有 Fixnums、符号、nil 或布尔值的那些之外的每个本质上都是指向 a 的指针RVALUE,其他的都直接在VALUE.

您的最后一个问题,您如何检查 aVALUE代表什么?您可以使用TYPE(x)宏来检查 aVALUE的类型是否是“原始”类型之一。

于 2011-08-13T15:56:20.230 回答
5
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);
于 2011-08-13T15:16:12.780 回答