'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