Ruby 1.9.1-p378 C расширение rb_block_call Странность - PullRequest
2 голосов
/ 10 июля 2010

Я работаю с тем, что должно быть довольно простой итерацией.Я понимаю, что мог бы сделать это с помощью кода 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 data2)

Вызывает метод в 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 и 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;
}

Внутренние компоненты Ruby, кажется, не имеют много примеров rb_block_call с argc / argv ... Не более одного или двух, иЯ считаю, что все они просто передают значения внутренне, а не используют их.

Мысли?

1 Ответ

4 голосов
/ 11 июля 2010

Я довольно новичок в расширении 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 - аргументы блока.

Простой пример - инъекция

Вот перевод на С: [1,2,3] .inject {| 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 принимает любое количество аргументов, поскольку при попытке передать некоторые из них не возникло никаких ошибок.

...