Metaprogramming in the Ruby C API: Part Two: Dynamic Methods

In the first article we discussed how to use blocks in the C API; in particular how to yield to a block from a method and how to convert a block to a proc. In this article we will utilize that knowledge and learn how to  dynamically create methods for both classes and objects,  we will also learn how to use the method_missing hook from C.

As a prelude to our discussion, however, we should cover some terminology.

Important Terms

There is a glut of terminology surrounding the Ruby Meta programming framework; words like Singleton, Eigenclass, Metaclass, Virtual class etcetera. Patrick Farley has rationalized most of these and I will adopt his usage here:

Singleton Class: Hidden Ruby class for storing instance specific behavior.

Metaclass: The Singleton class of a Class object. Differs in some ways from a “normal” Singleton class. Occasionally inappropriately used as a synonym for Singleton class.

Eigenclass: Alternative name for a Singleton Class. Eigenclass (roughly “own class” in German) has the advantage of not having a well known OO design pattern named after it. It’s preferred by some of the Ruby elite, but has never really caught on with the community at large.

Virtual Class: Alternative name for a Singleton Class. Best avoided. In the current MRI implementation, there is nothing particularly virtual about these classes and virtual is already a very loaded term in the OO community.

With this terminology in mind we can return to our discussion.

Dynamic Definition of Methods

We will implement a function you should be familiar with, we will call it ‘add_method’,  and it is nothing more than a public wrapper for define_method:

class MyClass
    def self.add_method(name, &block)

        raise ArgumentError, "need a block" if !block

        define_method(name, block)
    end
end

And the C version:

#include <ruby.h>

VALUE jm_Class;

static VALUE
add_method(VALUE self, VALUE name) {
    VALUE p;

    rb_need_block();

    p = rb_block_proc();

    rb_funcall(self, rb_intern("define_method"), 2, name, p);

    return Qnil;
}

void
Init_myclass() {
    jm_Class = rb_define_class("MyClass", rb_cObject);
    rb_define_singleton_method(jm_Class,"add_method",add_method,1);
}

The C implementation follows the Ruby very closely. First, it checks if there’s a block and raises an exception if  one is not provided (using rb_need_block()).

Secondly, after converting the implicit block to a proc, the define_method function is invoked on ‘self’. Remember that add_method is a class method (not an instance method) and so the value of ‘self’ corresponds to the class (MyClass).

Finally, the effect of the define_method call is to create a new instance method for MyClass.

It is also interesting to note how ‘add_method’ became a class method. We know from Ruby that a class method is really an instance method on the class’s Singleton class (Metaclass), but in C this is made explicit: to define a class method we must use rb_define_singleton_method(). The prototype for this function is:

void rb_define_singleton_method(VALUE klass, char *name, VALUE(*func)(), int argc);

The parameters for rb_define_singleton_method() are identical to those for the regular rb_define_method(). The difference between the two functions are, of course, that the latter defines instance methods whereas the former defines class methods.

An Object-specific add_method

The goal here is to implement a version of add_method to provide instance-specific dynamic methods. We know that instance-specific behaviour is possible through the use of an Object’s Singleton class; the following Ruby code does what we want:

class MyClass
    def add_method(name, &block)
        raise ArgumentError, "need a block" if !block

        sing = class << self; self; end

        sing.send(:define_method, name, block)
    end
end
&#91;/sourcecode&#93;

Here is the C version:

&#91;sourcecode language='cpp'&#93;
#include <ruby.h>

VALUE jm_Class;

static VALUE
add_method(VALUE self, VALUE name) {
    VALUE proc, sing;

    rb_need_block();

    proc = rb_block_proc();

    sing = rb_singleton_class(self);

    rb_funcall(sing, rb_intern("define_method"), 2, name, proc);

    return Qnil;
}

void
Init_myclass() {
    jm_Class = rb_define_class("MyClass", rb_cObject);
    rb_define_method(jm_Class,"add_method",add_method,1);
}

Note the differences between this C code and the C code in the class example. First note that the Ruby binding for add_method is a regular instance method (implemented using rb_define_method()), whereas in the class example add_method was a class method. Also observe that the call to rb_funcall() is made on a variable called ‘sing’ and not simply ‘self’.

The only new function in the above code is rb_singleton_class(). Comparing the Ruby to the C it is easy to see that rb_singleton_class() is the C API equivalent of Ruby’s ‘class << self; self; end’ syntax, and both operations return the Singleton class for the object. Recall that the reason we need to access the Singleton class is so we can define our instance specific methods on it. Here is the prototype for rb_singleton_class():

VALUE rb_singleton_class(VALUE obj);

Where the parameter ‘obj’ is any object (including classes) and the return value is the Singleton class of that object.

Method_missing

The method_missing hook is a major component of Ruby’s Metaprogramming toolbox. It is easy to use from C, we simply have to define a method with the name “method_missing”.

To illustrate the use of this hook we will implement a simple system to support case-insensitive method invocation, here is the Ruby code:

class MyClass

    def method_missing(sym, *argv)

        method_name = sym.to_s.downcase

        if respond_to?(method_name) then
            send(method_name, *argv)
        else
            raise NotImplementedError, "no such method #{method_name}"
        end

    end

end

And the C equivalent:

#include <ruby.h>

VALUE jm_Class;

static VALUE
m_missing(int argc, VALUE * argv, VALUE self) {

    VALUE method_name;

    method_name = rb_funcall(*argv, rb_intern("to_s"), 0);

    method_name = rb_funcall(method_name, rb_intern("downcase"),0);

    if(rb_respond_to(self, rb_to_id(method_name))) {
        rb_funcall2(self, rb_to_id(method_name), --argc, ++argv);
    }
    else {
        rb_raise (rb_eNotImpError, "no such method: %s", StringValuePtr(method_name));
    }

    return Qnil;
}

void
Init_myclass() {
    jm_Class = rb_define_class("MyClass", rb_cObject);
    rb_define_method(jm_Class,"method_missing",m_missing, -1);
}

The first thing to note about the C implementation is that the “method_missing” binding is set to have a variable number of parameters (the -1 argument to rb_define_method). This is the typical setup for method_missing if we wish to hold onto the passed parameters.

Also observe that rb_funcall2() is used inside the if-block instead of the typical rb_funcall(); rb_funcall2() is more useful to us here as it allows us to pass the parameters as a C array, obviating the need for any Ruby-Array manipulations.  Here is the prototype for rb_funcall2():

VALUE rb_funcall2(VALUE recv, ID id, int argc, VALUE *args);

Another function you may not recognize is rb_to_id(); here is its prototype:

ID rb_to_id(VALUE);

Where the parameter must be either a Ruby String or Symbol and the return value is the associated ID for that name (Note that this function is different to rb_intern() which takes a char*).

The above C and Ruby code should perform pretty much identically.

Summary

From dynamic method creation, to class methods, and Singleton classes to method_missing we have covered alot of ground in this article.
The next article will focus on the *_eval family of functions and will culminate in the writing of our own ‘attr_accessor’ method in pure C.

For more in-depth coverage of some of the topics covered here please look at Patrick Farley’s blog as well as the Ruby Hacker’s Guide (translated from the Japanese).

5 Responses to “Metaprogramming in the Ruby C API: Part Two: Dynamic Methods”

  1. Thank you very much. It was an interesting read.

  2. Thank you very much, this was very helpful!

  3. Thanks !! it’s Helpful ;
    I’d like to know , how to to call a method in script.rb from C , coze here I dont know how to use the rb_funcall to invoke the methode in the specific script.rb( I mean I should give it a path , But How ??!!)

Trackbacks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: