Passing a block from a method written in c
January 22nd, 2012
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)
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.
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]
Leave a Reply