Ruby C扩展API问题

omn*_*nse 16 c ruby ruby-c-extension

所以,最近我不幸地需要为Ruby做一个C扩展(因为性能).由于我是有了解的问题VALUE(和仍然),所以我看着Ruby源代码,并发现:typedef unsigned long VALUE;(链接到源代码,但你会发现,有它做了一些其他的"方式",但我认为它本质上是一个long; 如我错了请纠正我).因此,在进一步调查时,我发现了一篇有趣的博文,其中说:

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

是什么让我困惑的是,当我试图将一个字符串在Ruby传递到C,并使用RSTRING_PTR();VALUE(通过在Ruby的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));
}
Run Code Online (Sandbox Code Playgroud)

此示例始终返回1作为字符串长度:

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

有时在ruby中我看到一个异常,说"无法将模块转换为字符串"(或者沿着这些行, 但是我正在弄乱代码,试图解决这个问题,我现在无法重现错误当我尝试时会发生错误StringValuePtr();[我有点不清楚这究竟是做什么的.文档说它将传递的参数更改为char*inp):

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

所以,有问题的Ruby代码是: MyMod::test("blahblablah")

编辑:修正了一些拼写错误并稍微更新了帖子.


问题

  1. 究竟是什么VALUE imp举办?指向对象/值的指针?价值本身?
  2. 如果它拥有值本身:它什么时候这样做,有没有办法检查它?
  3. 我如何实际访问该值(因为我似乎访问除了 值之外的几乎所有内容)?

PS:我对C的理解并不是最好的,但这是一项正在进行的工作; 另外,请阅读代码片段中的注释以获取一些其他说明(如果有帮助).

谢谢!

emb*_*oss 29

Ruby Strings vs. C字符串

让我们先从字符串开始.首先,试图找回在C柱之前,它是好习惯叫StringValue(obj)上你的VALUE第一次.这确保你最终会真正处理Ruby字符串,因为如果它不是一个字符串,那么它会通过调用该对象的to_str方法将其转换为一个字符串.因此,这会使事情变得更安全,并防止偶尔发生的段错误.

接下来需要注意的是Ruby字符串没有被\0终止,因为你的C代码会期望它们能像strlen预期的那样工作.Ruby的字符串带有它们的长度信息 - 这就是为什么除了RSTRING_PTR(str)还有RSTRING_LEN(str)宏来确定实际长度.

所以StringValuePtr现在做的是将非零终止返回char *给你 - 这对于你有一个单独长度但不是你想要的缓冲区来说非常好strlen.用StringValueCStr相反,它会修改字符串是零结尾所以这是在C是希望它是零结尾功能的使用是安全的.但是,尽可能避免这种情况,因为这种修改比检索不需要修改的非零终止字符串要差得多.令人惊讶的是,如果你密切注意这一点你实际上需要"真正的"C字符串很少.

self作为隐式VALUE参数

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

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

    VALUE doit(VALUE self)

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

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

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

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

在Ruby中.所以你在你的例子中所做的不是 Ruby传递给你的字符串,而是实际上当前的值self,即调用该函数时接收器的对象.你的例子的正确定义是

static VALUE test(VALUE self, VALUE input) 
Run Code Online (Sandbox Code Playgroud)

static指出要在C扩展中遵循的另一条规则.如果您打算在多个源文件中共享它们,请仅将C函数设置为公共.由于您附加到Ruby类的函数几乎不会出现这种情况,因此您应该static默认将它们声明为只有在有充分理由这样做的情况下才公开它们.

什么是VALUE以及它来自哪里?

现在到了更难的部分.如果你深入研究Ruby内部,那么你将在gc.c中找到函数rb_objnew.在这里,您可以看到任何新创建的Ruby对象都是VALUE通过被称为的对象转换为一个freelist.它被定义为:

#define freelist objspace->heap.freelist
Run Code Online (Sandbox Code Playgroud)

您可以将其想象objspace为一个巨大的映射,用于存储代码中给定时间点当前处于活动状态的每个对象.这也是垃圾收集者履行职责heap的地方,特别是结构是新物体诞生的地方.堆的"空闲列表"再次被声明为一个RVALUE *.这是Ruby内置类型的C内部表示.一个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;
Run Code Online (Sandbox Code Playgroud)

也就是说,基本上是Ruby知道的核心数据类型的联合.遗漏了什么?是的,那里nil不包含Fixnums,Symbols 和布尔值.这是因为这些类型的对象是使用直接代表的unsigned long是一个VALUE归结到底.我认为那里的设计决策(除了一个很酷的想法)解除引用指针的效果可能比转换为VALUE实际代表的当前所需的位移效果稍差.实质上

obj = (VALUE)freelist;
Run Code Online (Sandbox Code Playgroud)

说给我目前任何freelist积分,治疗就是unsigned long.这是安全的,因为freelist是一个指针RVALUE- 指针也可以安全地解释为unsigned long.这意味着VALUE除了那些携带Fixnums,symbol,nil或Booleans之外的每一个都基本上指向a RVALUE,其他的直接表示在VALUE.

你的上一个问题,你如何检查代表什么VALUE?您可以使用TYPE(x)宏来检查某个VALUE类型是否是"原始"类型之一.


Mon*_*uïe 5

VALUE test(VALUE inp)
Run Code Online (Sandbox Code Playgroud)

第一个问题是:inp是self(因此,在您的情况下,模块).如果你想引用第一个参数,你需要在它之前添加一个self参数(这使我添加-Wno-unused-parameters到我的cflags中,因为它从未在模块函数的情况下使用):

VALUE test(VALUE self, VALUE inp)
Run Code Online (Sandbox Code Playgroud)

您的第一个示例使用模块作为字符串,这当然不会产生任何好处.RSTRING_PTR缺乏类型检查,这是不使用它的好理由.

VALUE是对Ruby对象的引用,但不是直接指向它可能包含的内容(如字符串中的char*).您需要使用某些宏或函数来获取该指针,具体取决于每个对象.对于字符串,您希望StringValuePtr(或StringValueCStr确保字符串以空值终止)返回指针(它不会以任何方式更改 VALUE的内容).

strlen(StringValuePtr(thing));
RSTRING_LEN(thing); /* I assume strlen was just an example ;) */
Run Code Online (Sandbox Code Playgroud)

VALUE至少在MRI和YARV中,object_id物体的实际内容是物体的(或者至少是在移位后).

对于您自己的对象,VALUE很可能包含指向您可以使用的C对象的指针Data_Get_Struct:

 my_type *thing = NULL;
 Data_Get_Struct(rb_thing, my_type, thing);
Run Code Online (Sandbox Code Playgroud)