Ruby C扩展API问题

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

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

令我困惑的是,当我尝试从Ruby传递字符串到C时,并使用RSTRING_PTR();VALUE (从Ruby传递给C函数),并尝试使用strlen();进行’调试’ 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中我看到一个exception,说“无法将模块转换为字符串”(或者沿着那些行, 然而,我正在弄乱代码,试图解决这个问题,我现在无法重现错误 当我尝试StringValuePtr();时会发生错误StringValuePtr(); [我有点不清楚这到底是做什么的。 文档说它将传递的参数更改为inp上的char* ]:

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

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

编辑 :修正了一些拼写错误并稍微更新了post。


问题

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

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

谢谢!

Ruby Strings与C字符串

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

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

那么StringValuePtr现在做的是将非零终止的char *给你 – 这对于你有一个单独长度的缓冲区很有用,但不是你想要的例如strlen 。 使用StringValueCStr ,它会将字符串修改为零终止,以便在C语言中使用期望它为零终止的函数时是安全的。 但是,尽可能避免这种情况,因为这种修改比检索不需要修改的非零终止字符串要差得多。 令人惊讶的是,如果你密切关注这一点你实际上很少需要“真正的”C字符串。

self作为隐式VALUE参数

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

  • 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) 

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

什么是VALUE,它来自哪里?

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

 #define freelist objspace->heap.freelist 

您可以将objspace想象成一个巨大的映射,它存储代码中给定时间点当前存活的每个对象。 这也是垃圾收集器履行职责的地方,特别是heap结构是新对象诞生的地方。 堆的“ RVALUE * ”再次被声明为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; 

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

 obj = (VALUE)freelist; 

说给我目前任何freelist点,并且对待是unsigned long 。 这是安全的,因为freelist是一个指向RVALUE的指针 – 指针也可以安全地解释为unsigned long 。 这意味着除了携带Fixnums,symbols,nil或Booleans之外的每个VALUE基本上都是指向RVALUE指针,其他VALUE直接表示在VALUE

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

 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获取该Data_Get_Struct

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