rubyruby-c-extension

Ruby and C API: rb_funcall_with_block on global method? How to call ruby global method with block from C?


I want to call a global method in ruby from my C API code. So in ruby:

def giveMeABlock(*args)
  puts "Starting giveMeABlock with #{args.inspect}"
  yield if block_given?
end

As I've since learned, global functions are actually just private functions in Object, so we can call them from anywhere.

And in C I want to call this method, I can do use rb_funcallv:

VALUE rb_funcallv(VALUE recv, ID mid, int argc, VALUE *argv)
Invokes a method, passing arguments as an array of values. Able to call even private/protected methods.

For this specific example I can do:

rb_funcallv(self, rb_intern("giveMeABlock"), 0, NULL);

And we are able to call the method, though no block is supplied.

To call with a block we have:

VALUE rb_funcall_with_block(VALUE recv, ID mid, int argc, const VALUE *argv, VALUE passed_procval)
Same as rb_funcallv_public, except passed_procval specifies the block to pass to the method.

But this, like rb_funcallv_public, can only call public methods. Which means if I try:

rb_funcall_with_block(self, rb_intern("giveMeABlock"), 0, NULL, block);

I get:

private method `giveMeABlock' called for main:Object (NoMethodError)

So why is there no funcall for private methods that can provide a block, or am I missing something? And how do I accomplish this seemingly simple task?

I have realized that I can define the method inside the Object class and then it works (since now it's public), but this seems hacky and assumes I have the ability to alter the ruby source.


Solution

  • I'm late to the party, and you probably don't need this answer anymore, but I'll post for posterity:

    The workaround I found for this was to just make the function public. You can either do it directly from the Ruby code:

    public def giveMeABlock(*args)
      puts "Starting giveMeABlock with #{args.inspect}"
      yield if block_given?
    end
    

    Or if that's not an option (e.g. if you can't change the Ruby code you need to run), you can make it public from C code:

    rb_funcall(rb_cObject, rb_intern("public"), 1, rb_id2sym(rb_intern("giveMeABlock")))
    

    Now that it's public, you can call it with rb_funcall_with_block.