Ruby 1.9.1-p378 C 扩展 rb_block_call 怪异

发布于 2024-09-09 05:19:19 字数 4084 浏览 4 评论 0原文

我正在处理一个相当基本的迭代。我知道我可以使用 Ruby 代码来完成它,但我已经在 C 扩展中工作,所以我更愿意将此函数与其余代码一起保留在 C 中 - 特别是因为这个应该可以工作(以一种或另一种方式)没有问题。

问题出在 rb_block_call 上。以下是 README.EXT 描述 rb_block_call 的方式:

VALUE rb_block_call(VALUE recv, ID mid, int argc, VALUE * argv,
          VALUE (*func) (ANYARGS),VALUE 数据2)

调用recv上的方法,使用 由符号指定的方法名称 mid,提供 func 作为块。 func将从yield中接收值 作为第一个参数,data2 作为 第二,argc/argv 作为 第三/第四个参数。

因此,我的理解(通过查看 Ruby 内部结构进行验证)是接收函数应该如下所示:

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 和设置为 Qtrue 的 VALUE 的结果相同。 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;
}

Ruby 内部似乎没有很多 rb_block_call 与 argc/argv 的示例...最多一两个,我相信它们都只是在内部中继值而不是使用它们。

想法?

I'm working with what should be a fairly basic iteration. I understand that I could accomplish it with Ruby code, but I am working already in a C extension, so I would prefer to keep this function in C with the rest of the code- especially since this should work (one way or another) without issue.

The issue is with rb_block_call. Here is how README.EXT describes rb_block_call:

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

Calls a method on the recv, with the
method name specified by the symbol
mid, supplying func as the block.
func will receive the value from yield
as the first argument, data2 as the
second, and argc/argv as the
third/fourth arguments.

So, my understanding (verified by looking at Ruby internals), is that the receiving function should look like:

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

And here we hit our problem. In my use case (which I will include below), rb_yield_value and data2 are passed as expected; argc, on the other hand, is always set to 1, argv[ 0 ] is rb_yield_value, argv[ 1 ] is false, argv[ 2 ] is rb_yield_value, argv[ 3 ] throws an exception.

It does not matter what I pass for argc and argv; passing 0 and NULL results the same, as does 1 and a VALUE set to Qtrue. Everything with argc/argv remains as described.

Here is the code I am working with:

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;
}

Ruby internals don't seem to have many examples of rb_block_call with argc/argv... At most one or two, and I believe they all simply relay the values internally rather than using them.

Thoughts?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

青丝拂面 2024-09-16 05:19:27

我对 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].inject { |sum, e| sum + 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 接受任意数量的参数,因为当您尝试传递一些参数时它不会引发任何错误。

I am pretty new to Ruby C extension, but I think where your confusion is.

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

argc/argv are here the arguments to the Ruby function you call.

In the C-function called as a block:

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

argc/argv are the arguments of the block.

A simple example is inject

Here is the C translation of: [1,2,3].inject { |sum, e| sum + 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);
}

which outputs (of a call to 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

I believe yield_value is always argv[0]

If you want to pass information between the block and the caller, then use data2

In your example, I suppose #each_backtrace_frame is yielding one "backtrace_frame" and so that is the reason argc/argv of the block is always 1/the_backtrace_frame. I believe #each_backtrace_frame accepts any number of arguments since it did not raise any error when you tried to pass some.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文