Techioz Blog

Ruby と C API: グローバル メソッドの rb_funcall_with_block? Cからブロックを使用してRubyグローバルメソッドを呼び出すにはどうすればよいですか?

概要

C API コードから Ruby のグローバル メソッドを呼び出したいと考えています。したがって、Rubyでは次のようになります。

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

その後学んだことですが、グローバル関数は実際には Object の単なるプライベート関数であるため、どこからでも呼び出すことができます。

C ではこのメソッドを呼び出したいので、 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.

この特定の例では、次のことができます。

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

また、ブロックが指定されていないにもかかわらず、メソッドを呼び出すことができます。

ブロックを使用して呼び出すには、次のようにします。

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.

ただし、これは rb_funcallv_public と同様、パブリック メソッドのみを呼び出すことができます。つまり、試してみると:

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

わかりました:

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

では、なぜブロックを提供できるプライベート メソッドの funcall がないのでしょうか、それとも何かが足りないのでしょうか?そして、この一見単純なタスクを達成するにはどうすればよいでしょうか?

Object クラス内でメソッドを定義すると、それが機能することがわかりました (パブリックになっているので)。しかし、これはハックのようで、Ruby ソースを変更できることが前提になっています。

解決策

パーティーに遅れてしまったので、おそらくこの回答はもう必要ないでしょうが、後世のために投稿しておきます。

私が見つけたこの問題の回避策は、関数をパブリックにすることでした。 Ruby コードから直接実行することもできます。

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

または、それができない場合 (たとえば、実行する必要がある Ruby コードを変更できない場合)、C コードからコードを公開できます。

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

これでパブリックになったので、rb_funcall_with_block で呼び出すことができます。