Skip to content

QtRuby, Korundum and Wt::Ruby ported to Ruby 1.9.1

Tuesday, 3 February 2009  |  richard dale

The implementation of the Ruby runtime in the new 1.9.1 release is a complete rewrite based on a virtual machine, YARV, instead of interpreting the AST directly and slowly like the previous version. So I was expecting that there would be a lot of changes required for QtRuby as it has a fair amount of C interface code. However, it turned out to be not so bad at all, and I'm pleased to announce that the QtRuby, Korundum and Wt::Ruby projects will all now build against Ruby 1.9.1 as well as the older 1.8.x versions.

A couple of months ago Davor Ocelic posted a patch from a 'Mr Napalm' (clearly da bomb!) to make QtRuby 1.4.10 build with Ruby 1.9. Davor ported the code in the patch to the trunk version of QtRuby and we added that. There were some changes though that needed some '#ifdefs' on the Ruby version and conditional compilation, and I added them yesterday.

The cmake detection for 1.9 doesn't seem to be 100% yet, and it got a bit confused with my 1.8 installation in /usr and 1.9 installation in /usr/local. I needed to hand tweak the CMakeCache.txt a bit to correct the paths of the Ruby lib, and Ruby headers. Ruby doesn't come with a version number in a header that you can use for #ifs, and I needed to add some cmake stuff to get a RUBY_VERSION from the Ruby interpreter to pass to the build as a '-D' macro symbol.

The were some changes to the Array and String handlers macros used in the C interface, like this:

    # Old Ruby
    int count = RARRAY(list)->len;
    memcpy((void *) data, (const void *) RSTRING(data_value)->ptr, RSTRING(data_value)->len);

    # New Ruby
    int count = RARRAY_LEN(list);
    memcpy((void *) data, (const void *) RSTRING_PTR(data_value), RSTRING_LEN(data_value));

We added these macros to the qtruby.h header so they were defined if missing, and that meant nearly all the code was the same with no need for #ifs on the Ruby version. There was an obscure function for getting the name of the last function called that needed conditional compilation:

#if RUBY_VERSION >= 0x10900
	QLatin1String signalname(rb_id2name(rb_frame_callee()));
#else
	QLatin1String signalname(rb_id2name(rb_frame_last_func()));
#endif

The biggest difference was in the QString <-> Ruby String marshalling code because in Ruby 1.9 each string has its own individual encoding. In Ruby 1.8.x there was a global variable $KCODE which you set according to which encoding you wanted (usually utf8 for Qt programs) and all of your strings worked that way. Now you can add an encoding comment at the top of your program like this:

# encoding: utf-8

The downside of this is that whenever the QtRuby runtime marshalls a Ruby string to a QString it needs to obtain the encoding and use an appropriate conversion, usually utf8 to utf16. This may be a bit slower than before and will take away some of the speed advantages of YARV. I haven't actually done any tests yet on the speed or memory consumption of Ruby 1.9.

For embedded Ruby code the way you start the Ruby interpreter has changed slightly, instead of ruby_run() you call ruby_run_node() like this:

#ifdef RUBY_INIT_STACK
    RUBY_INIT_STACK
#endif
    ruby_init();
    ruby_init_loadpath();
    ruby_incpush(QFile::encodeName(program.path()));
#if RUBY_VERSION < 0x10900
    ruby_options(argc+1, rubyargs); 
    ruby_script(QFile::encodeName(program.fileName()));
    ruby_run();
#else
    ruby_script(QFile::encodeName(program.fileName()));
    ruby_run_node(ruby_options(argc+1, rubyargs));
#endif

In the Wt::Ruby code examples, the main change I needed to make was to case statements, as you can no longer terminate the condition part of a case with a colon:

      # Old style works with a colon:
      case foo
      when Wt::WValidator::Valid:
        puts "valid"
        ...

     # Remove the colon for 1.9
     case foo
      when Wt::WValidator::Valid
        puts "valid"
        ...

Single character literals are now strings of length 1, instead of an integer value. I added an '.ord' call to them so that the same code would work with both old and new Ruby. For example:

    # Old Ruby will return 97 and new Ruby "a"
    ?a 

   # This will return 97 under both interpreters:
   ?a.ord

And that is about it. I'm really looking forward to getting a copy of the new Pickaxe with a full write up of the differences, but in the meantime Changes in Ruby 1.9 is a good summary.