I spent most of yesterday and today hacking out the first spike of the Ruby rewrite of my original Ruby to CLR bridge. I wound up adding some additional code to RbDynamicMethod to support some scenarios that I didn’t envision (nothing like having a ‘customer’ to reveal deficiencies in an API!). I also spent a lot of time learning about how objects are constructed in Ruby. Here’s a very simplified explanation of Ruby’s object model; for a complete description, see Chapter 24 of Dave Thomas’ excellent Programming Ruby.
In Ruby, objects are instances of classes. However, classes are also objects in Ruby, which means that a class is an instance of a class called Class. Let’s consider the following code fragment:
a = ArrayList.new
puts a.Count
In the first line, we’ve created an instance of class ArrayList and assigned it to the variable a. Next, we invoke the Count instance method of the ArrayList object. So far so good, right?
Next let’s see how Ruby invokes instance methods. Every Ruby object contains a reference to its class object. This is stored in an internal field called klass. When we invoke the Count method on the ArrayList object, Ruby follows the klass reference to find the ArrayList class object. It searches its method table, finds the Count method and invokes it.
In my bridge, I delay binding to a method on a CLR object until it is called. When it is called, I build a small piece of CIL code using RbDynamicMethod to call the method and marshal data back to the caller. I can delay binding to the method using the method_missing instance method of the object. In the case of the call to the Count method, you could imagine a piece of code that looks like:
alias alias_method_missing method_missing
def method_missing(name, *params)
alias_method_missing(name, *params) unless name == :Count
create_ruby_instance_method(self.class, 'Count') do
include 'System.Collections'
ldarg_2
call 'static Marshal::ToClrObject(VALUE)'
call 'ArrayList::get_Count()'
call 'static Marshal::ToRubyNumber(Int32)'
ret
end
self.Count
end
This is a hard-coded example that generates a shim for the Count method. The shim invokes the get_Count method to retrieve the value of the Count property of the ArrayList object. It marshals the return value (an Int32) back to the caller using the Marshal::ToRubyNumber() helper method in the RbDynamicMethod library.
Where things get interesting is the first two lines of CIL code in the shim. Here, I reach into the Ruby object and pull out the actual ArrayList object reference that is stored in a secret field of the Ruby object. This secret field is completely inaccessible to Ruby code. Once I have the object reference, I can freely invoke the get_Count instance method using the CIL call instruction.
Notice that I’m defining an instance method called Count on the ArrayList class object. At the end of the method_missing method, I invoke the Count method that I just defined. However, all subsequent calls to the Count instance method will go directly to the CIL code that I just wrote (which of course will have been compiled into x86 code as well). This means that we will have excellent performance since we completely avoid having to use the Reflection APIs in the CLR.
You’re probably wondering how the ArrayList object reference got stored in the secret field? I’ll talk about that in tomorrow’s installment of the story when we look at how objects get created in Ruby (and it’s nowhere near as simple as it looks!).
Recent Comments