« Quick ports with RubyCocoa | Main | RubyCocoa self-contained app working! »

February 20, 2005

Analysis of RubyCocoa's internals

I spent some time today studying how RubyCocoa is implemented, since I like the concept of glue layers between languages and wanted to compare RubyCocoa with other layers I've seen before. After learning how it works, I'm very impressed with how RubyCocoa is implemented.

It has three main components. First, there is an ObjC header parsing script that finds all official ObjC headers on your system and generates ruby extension glue code that creates ruby wrapper objects for them. The parser is like SWIG but specialized to make the generated APIs more idiomatic to ruby developers. It's a little under 380 lines of code, which is made possible because Ruby's C extension API and interpreter data structures are pretty simple and well-designed.

The second component is a script that parses the major framework headers to find their class names, creating a small set of ruby libraries that just contain a generated set of class import commands. For example, the generated AppKit.rb looks at AppKit.h and notes that it has to load NSView, NSControl, etc, and creates import statements in ruby that register those classes for wrapping by the bridge.

The third component is a runtime bridge/glue layer that implements the imports that the second component calls. The imports create thin ObjC wrapper objects that only hold what class they wrap plus a small amount of metadata. When you create a OSX::NSArray object in ruby, the bridge code tells the ObjC runtime to create a NSArray object and store it its reference, which is then passed by the bridge back to the ruby OSX::NSArray wrapper class and stored away.

When you later make a method call on your wrapped array, the glue code simply parses your method to determine what selector was used, sending that selector to the object referenced by the id it stored earlier. A small library of type mapping code is used to massage arguments and return values when they are passed across the bridge. If the wrapped object doesn't support the selector, then an error is returned back to the ruby layer.

This is very elegant, since you're not updating hand-maintained glue code every time Apple releases software updates, and the glue is small and straightforward. This wouldn't have been possible with C++, but ObjC is dynamic enough to let you specify method names and receivers at runtime. A very cool hack that would have been a lot more LOC with other implementations and much harder to maintain as well. I love to see dynamic stuff like this.

And a note re: performance, the implementation of RubyCocoa makes it much easier to implement performance tuning and tune things like roundtrip latency across the bridge for method calls. Perhaps I will do some timings on the bridge to find hot spot candidates for inlining the Ruby code with C. There are much larger and more uniform gains to be made in performance with this kind of wrapper implementation versus a traditional fully hand maintained glue layer (writing C code utilizing Ruby's extension API) or a partially hand maintained glue layer like SWIG. I'm confident that RubyCocoa can be sped up.

Speaking of dynamic generation of glue code, I learned yesterday that JOGL uses something similar, and that one of JOGL's developers half-jokingly said their glue generation code was more valuable than the JOGL bindings themselves, since it could apply to creating JNI wrappers for other C libraries. I would argue they didn't have to say that half-jokingly; I'm learning more and more that a well-designed glue system is worth its weight in gold.

Posted by djb at February 20, 2005 02:47 AM

Comments

Post a comment




Remember Me?

(you may use HTML tags for style)