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。
问题
-
VALUE imp
究竟拥有什么? 指向对象/值的指针? 价值本身? - 如果它保留了值本身:它什么时候这样做,有没有办法检查它?
- 我如何实际访问该值(因为我似乎访问除了值之外的几乎所有内容)?
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);