2010-07-10 58 views
2

我正在与什么应该是一个相当基本的迭代。我知道我可以用Ruby代码来完成它,但我已经在C扩展中工作,所以我宁愿用C代码保留这个函数 - 尤其是因为这个应该工作(单向或者其他方式) )没有问题。Ruby 1.9.1-p378 C扩展rb_block_call奇怪

问题在于rb_block_call。这里是README.EXT如何描述rb_block_call:

VALUE rb_block_call(VALUE recv, ID mid, int argc, VALUE * argv, 
      VALUE (*func) (ANYARGS), VALUE data2) 

呼吁对recv的方法,由符号 中期指定的 方法名,提供FUNC为块。 func将从yield 作为第一个参数获得值,data2作为 秒,argc/argv作为 第三个/第四个参数。

所以,我的理解(通过看红宝石内部验证),是接收函数应该是这样的:

VALUE function(VALUE rb_yield_value, VALUE data2, int argc, VALUE argv); 

在这里,我们打我们的问题。在我的用例中(我将在下面介绍),rb_yield_value和data2按预期传递;另一方面,argc始终设置为1,argv [0]为rb_yield_value,argv [1]为false,argv [2]为rb_yield_value,argv [3]引发异常。

无论我为argc和argv传递什么,传递0和NULL结果相同,1和VALUE设置为Qtrue。所有与argc/argv保持一致的描述。

这里是我一起工作的代码:

VALUE rb_RPBDB_DatabaseObject_internal_cursorForCallingContext(VALUE rb_self) { 

    // when we are looking for the contextual iterator, we look up the current backtrace 
    // at each level of the backtrace we have an object and a method; 
    // if this object and method match keys present in self (tracking calling contexts for iteration in this iteration class) return cursor 

    VALUE rb_cursor_context_storage_hash = rb_RPBDB_DatabaseObject_internal_cursorContextStorageHash(rb_self); 

    VALUE rb_cursor = Qnil; 

    if (RHASH_SIZE(rb_cursor_context_storage_hash)) { 

     rb_block_call( rb_mKernel, 
         rb_intern("each_backtrace_frame"), 
         1, 
         & rb_cursor_context_storage_hash, 
         rb_RPBDB_DatabaseObject_internal_each_backtrace_frame, 
         rb_cursor);  
    } 

    return rb_cursor; 
} 

// walk up the stack one frame at a time 
// for each frame we need to see if object/method are defined in our context storage hash 
VALUE rb_RPBDB_DatabaseObject_internal_each_backtrace_frame( VALUE rb_this_backtrace_frame_hash, 
                   VALUE rb_cursor_return, 
                   int  argc, 
                   VALUE* args) { 

    // why are we getting 3 args when argc is 1 and none of the 3 match what was passed? 
    VALUE rb_cursor_context_storage_hash = args[ 0 ]; 

    // each frame is identifiable as object/method 
    VALUE rb_this_frame_object = rb_hash_aref( rb_this_backtrace_frame_hash, 
                 ID2SYM(rb_intern("object"))); 
    VALUE rb_this_frame_method = rb_hash_aref( rb_this_backtrace_frame_hash, 
                 ID2SYM(rb_intern("method"))); 

    // we likely have "block in ..." for our method; we only want the "..." 
    rb_this_frame_method = ID2SYM(rb_to_id(rb_funcall( rb_obj_as_string(rb_this_frame_method), 
                   rb_intern("gsub"), 
                   2, 
                   rb_str_new2("block in "), 
                   rb_str_new2("")))); 

    VALUE rb_cursor_object_context_hash = rb_RPBDB_DatabaseObject_internal_cursorObjectContextStorageHash( rb_cursor_context_storage_hash, 
                                rb_this_frame_object); 

    if (RHASH_SIZE(rb_cursor_object_context_hash)) { 

     rb_cursor_return = rb_hash_aref( rb_cursor_object_context_hash, 
               rb_this_frame_method); 

    } 

    return rb_cursor_return; 
} 

红宝石内部似乎并没有与ARGC/argv的rb_block_call的例子很多......最多一两个,我相信他们都只需在内部传递值而不是使用它们。

想法?

回答

4

我对Ruby C扩展很新,但我认为你的困惑在哪里。

VALUE rb_block_call(VALUE recv, ID mid, int argc, VALUE argv[], 
    VALUE (*func) (ANYARGS), VALUE data2) 

argc/argv在这里是您调用的Ruby函数的参数。

在称为块中的C-功能:

VALUE block_function(VALUE rb_yield_value, VALUE data2, int argc, VALUE argv[]) 

的argc/argv的是该块的参数。

一个简单的例子是注入

这里是C的翻译:[1,2,3]。注入{| sum,e |总和+ E}

#include "ruby.h" 

static VALUE rb_puts(VALUE obj) { 
    return rb_funcall(rb_mKernel, rb_intern("puts"), 1, obj); 
} 

static VALUE inject_block(VALUE yield_value, VALUE data2, int argc, VALUE argv[]) { 
    printf("\nyield_value:\n"); 
    rb_puts(yield_value); 
    printf("data2:\n"); 
    rb_puts(data2); 
    printf("argc: %d\n", argc); 
    printf("argv:\n"); 
    int i; 
    for(i = 0; i < argc; ++i) { 
    printf("argv %d:\n", i); 
    rb_puts(argv[i]); 
    } 

    VALUE sum = argv[0]; 
    VALUE e = argv[1];// or yield_value 
    return INT2FIX(FIX2INT(sum) + FIX2INT(e)); 
} 

static VALUE rb_block_call_test(int argc, VALUE argv[]) { 
    VALUE ary = rb_ary_new(); 
    int i; 
    for(i = 0; i < 3; ++i) { 
    rb_ary_push(ary, INT2FIX(i+1)); 
    } 
    VALUE block_argv[1]; 
    block_argv[0] = INT2FIX(0); 
    ary = rb_block_call(ary, 
       rb_intern("inject"), 
       1, // argc 
       block_argv, //argv is a C-array of VALUE 
       inject_block, 
       Qtrue // data2 
       ); 
    return ary; 
} 

void Init_rb_block_call() { 
    rb_define_global_function("rb_block_call_test", rb_block_call_test, 0); 
} 

其输出(的呼叫到rb_block_call_test):

yield_value: 0 # sum = argv[0] 
data2: true 
argc: 2 
argv: 
argv 0: 0 # sum 
argv 1: 1 # e 

yield_value: 1 
data2: true 
argc: 2 
argv: 
argv 0: 1 
argv 1: 2 

yield_value: 3 
data2: true 
argc: 2 
argv: 
argv 0: 3 
argv 1: 3 

# => 6 

相信yield_value总是ARGV [0]

如果要在块之间传递信息和来电者,然后使用data2

在你的例子中,我想#each_backtrace_frame正在产生一个“backtrace_frame”,所以这是该块的argc/argv的原因总是1/the_backtrace_frame。我相信#each_backtrace_frame可以接受任意数量的参数,因为当您尝试传递一些参数时它不会引发任何错误。

+0

确定这实际上是有意义的审查。这确实是一个文档错误,因为文档说argc/argv将被传递给func(这是一个参数),而不是中间指定的Ruby方法。文档应为: 在recv上调用方法,方法名称由 符号指定,argc参数在argv中,提供func作为块。当func作为块被调用时,它将从yield作为第一个参数接收 值,并将data2作为第二个参数。 – Asher 2010-07-11 10:20:13

+0

...和argc/argv作为第三/四个参数,作为块的生成值 – eregon 2010-07-11 10:40:15

+0

argc/argv是内部的,在我们解决API时没有意义引用。 API文档的措辞不正确。文档引用的argc/argv引用了rb_block_call,而不是内部的rb_yield。该文件充其量是令人困惑的,但在我看来相当明显和简单的错误。它应该更新。 – Asher 2010-07-12 04:51:51