Space Vatican

Ramblings of a curious coder

Passing a Block From a Method Written in C

Everynow and again I wind up rewriting a ruby performance hotspot in C. It happens infrequently enough that I always forget the C api for passing a block implemented in C to some ruby code. Hopefully writing this down will help me remember this in the future. Today, I wanted to call find_each on a class, using a C function as the block. Pre ruby 1.9 you need to call rb_iterate which always did my head in, but in 1.9 you use rb_block_call which is way more straightforward (rb_iterate is still there but deprecated)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static VALUE block_method(VALUE yielded_object, VALUE context, int argc, VALUE argv[]){
    /* do work here
  Don't forget to return a valid VALUE (eg. Qnil)
*/
}

static VALUE some_method(VALUE self){
    /*
      set klass to the class we want to call find_each on
      set ctx  to a pointer to some context 
    */
    VALUE args[1];
    args[0] = INT2FIX(0);
    return rb_block_call(klass, rb_intern("find_each"), 1, args, RUBY_METHOD_FUNC(block_method), (VALUE)ctx);
}

block_method is the C function that implements my block. some_method is the function that wishes to call find_each on klass passing the block.

rb_block_call takes the 4th argument you give it and sets things up so that the ruby method call made will use that method as a block. The last argument passed to rb_block_call is some context that is the second argument to your block method. On MRI you seem to be able to use an arbitrary pointer (rather than a ruby object) but this doesn’t work on rubinius.

The first 4 parameters are straight forward: the object to call the method on, what method to call and the arguments for that method, expressed as a length and an array of VALUE (here I am passing just one argument, 0, as an example).

If you block yields more than one value then they will be in the argc & argv parameters in block_method. As I understand it, yielded_object will always be the same as argv[0]