'How to pass context to a Ruby method defined in C/C++
I am currently writing a set of API bindings to the Ruby interpreter. The problem is that the binding definitions are not static and retrieved on initialization. I can easily create types on the fly, but in order to add methods I need static handlers:
static VALUE dispatchInstanceMethod( int argc, VALUE *argv, VALUE obj )
{...}
static VALUE dispatchClassMethod( int argc, VALUE *argv, VALUE obj )
{...}
VALUE cls = rb_define_class_id_under( myModule, nameID, rb_cObject );
rb_define_method( cls, "methodName", RUBY_METHOD_FUNC(dispatchInstanceMethod), -1 );
rb_define_singleton_method( cls, "methodName", RUBY_METHOD_FUNC(dispatchClassMethod), -1 );
However, this requires each method (both static and instance methods) to use different handlers.
Since I am generating these definitions on the runtime, I must use a generic dispatcher to
deal with all method calls. However, my dispatcher cannot know which method and of which class
was called. How can I retrieve that information? The class info could be retrieved
from the self
object passed to the handler, but in case of static method the self
is the
type itself. Can I attach any sort of user data to the Class type VALUE
? And how can I know
which Ruby method was called if each method uses the same dispatcher function?
A few other interpreters I have worked against in the past have provided a
generic void*
pointer for this kind of data (usually along with the release function), or some other mechanism, but there doesn't seem to be anything like that in Ruby. Have I missed it, or is such use simply not available?
Solution 1:[1]
This is a little more experimental. I'm not sure exactly how this will work, but it's based on Ruby's define_method for defining proper methods at runtime
VALUE context; // context for method call
VALUE dispatchInstanceMethod(VALUE first_arg, VALUE context, int argc, VALUE* argv)
{
// first_arg is the first arg passed to your method, probably shouldn't use it
// just use argc, argv instead for accessing all args
// context should(?) be what we passed at the time of definition
}
void dynamically_create_class()
{
VALUE cls = rb_define_class_id_under(myModule, nameID, rb_cObject);
ID define_method = rb_intern("define_method");
VALUE name = // method name as a Symbol object e.g. using ID2SYM
context = // set to whatever context we need for the call
rb_global_variable(context); // don't garbage-collect it
rb_block_call(cls, define_method, 1, &name, dispatchInstanceMethod, context);
}
For this to work (if it can work), you'd need a different context
variable for each method definition. The lifetime of these variables would have to exceed the life of the class, thus they'd need to be globally available somewhere. That's how you would differentiate dispatchInstanceMethod
later on when it actually gets called by each method.
Solution 2:[2]
There's nothing stopping you from doing this the normal Ruby way, by defining method_missing. That gives you a generic dispatcher. In Ruby it looks like this
class Foo
def self.method_missing symbol, args
# dispatch class method
end
def method_missing symbol, args
# dispatch instance method
end
end
In the C API you do this normal way, using rb_define_method
and rb_define_singleton_method
. I haven't tested it, but I imagine you can even pass the same handler to both and use the value of self
to determine which dispatch (class/instance) to do.
Solution 3:[3]
This is the kind of thing that's way easier to do in normal Ruby. I would write a C API method for making a generic API call using basic types (i.e. no custom classes). For example, something like
VALUE api_call(VALUE self, VALUE class_name, VALUE method_name, VALUE instance_variables, VALUE arguments)
{
// turn class_name, method_name, instance_variables, arguments into an API call
}
mAPI = rb_define_module("MyAPI");
rb_define_singleton_method(mAPI, "call", api_call, 4);
Then you can write regular old Ruby to dynamically create the class
class_def = {name: "Foo", ivars: ["bar"], methods: ["baz"]}
klass = Class.new
klass.instance_eval do
class_def[:ivars].each do |var|
attr_accessor var
end
class_def[:methods].each do |meth|
define_method(meth) do |*args|
ivars = Hash[instance_variables.map { |name| [name, instance_variable_get(name)] }]
MyAPI.call(class_def[:name], meth, ivars, args)
end
end
end
Kernel.const_set(class_def[:name], klass)
foo = Foo.new
f.bar = 5
f.baz("Hi") # MyAPI.call("Foo", "baz", {:@bar=>5}, ["Hi"])
Solution 4:[4]
To answer this part of the question:
And how can I know which Ruby method was called if each method uses the same dispatcher function?
You can do this will caller_locations, and you can accomplish that with rb_eval. So essentially:
VALUE myMethodName = rb_eval("caller_locations(1,1).first.label");
This will tell your C function what method it was called at. Note that we don't want caller_locations(0) because this is actually the eval() context which is back in the ruby code that called this method, and caller_locations(1) is the C method.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Max |
Solution 2 | Max |
Solution 3 | |
Solution 4 | David Ljung Madison Stellar |