2

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.

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

7 Comments

The end of the original post already recognizes that it can be made public, but again asks why this can't be done with a private method, why is this inconsistent? And again, it assumes I have the ability to edit the ruby source.
My answer addresses this, specifically for the case where you can’t edit the Ruby source.
test.cpp:184:58: error: invalid conversion from ‘VALUE’ {aka ‘long unsigned int’} to ‘const VALUE*’ {aka ‘const long unsigned int*’} [-fpermissive] 184 | rb_funcallv(rb_cObject, rb_intern("public"), 1, rb_id2sym(rb_intern("giveMeABlock")));
Ah, I see the problem - you used rb_funcallv for the public call and that needs an argv, but it should just be an rb_funcall. That solved that problem. I didn't have a block to pass to it but it did get past the initial error, so I've edited your answer and selected it. Thanks for the tip! Didn't realize you could make methods public ipso-facto!
@DavidLjungMadisonStellar Indeed! The private/protected/public keywords aren't actually keywords, they're methods on Module. They work in two ways, the more usual one, where they change the state of the current module or class, so new method definitions take on whichever access level was most recently specified. There's a second way, where you pass them symbols as args, and they'll change their visibility. Since a method definitions (def...end) evaluates to a symbol, that's what lets you write public def foo; ... end. It's calling self.public(:foo) on the module/class.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.