Skip to content

QtRuby 3.x refactor/rewrite started

Thursday, 28 April 2011  |  richard dale

I've been neglecting QtRuby recently, although I've wanted to do a major rewrite for some time. I finally bit the bullet last Thursday, and decided that I was going to take time off work and enter a hacking frenzy until the new version of QtRuby was well underway. After six days I've just got a 'hello world' working and commited the project to a 'qtruby-3.0' branch in the qtruby KDE repo.

QtRuby was started in mid-2003 and based on the PerlQt code by Ashley Winters, David Faure and Germain Gerand that they had written in the previous year. It uses the idea of language independent bindings libraries called 'Smoke', which was invented by Ashley Winters. Originally all the C++ libraries had to be wrapped into a single 'Smoke' library that the bindings interfaced to. The last major improvement was when Arno Rehn made the Smoke libraries modular and we changed QtRuby to work with them. Over the years the QtRuby code got moved around, and bits and pieces were hacked onto it. Despite our best efforts it has gradually turned into a bit of a mess, and I was finding it hard to maintain as I never quite knew where to find where a particular bit of functionality was implemented. The C++ code was in random C++ source files with names that didn't seem to mean much, and the functionality was often split into a Ruby part and a C/C++ part which could make following what was going on quite hard.

When I worked on the JSmoke QtScript bindings with Ian Monroe it was a chance to implement a Smoke based language binding 'done right'. I was able to use JSmoke as a testbed for improvements, and it ended up being much more elegant than the QtRuby code. So I wanted to base the new version of QtRuby on JSmoke.

The current QtRuby is still a single ruby extension for all the main Qt libs though, and one reason for the rewrite was to split it up into one ruby extension per Qt lib. So instead of a single require 'qtruby4' for all the Qt libs, each Qt lib will need its own require such as 'qtcore', 'qtgui' and so on.

One big technical improvement that I made for JSmoke was implementing C++ implicit type conversions for arguments to methods. I wrote a blog entitled Implementing C++ implicit type conversions on method arguments in Smoke based language bindings which described how that worked. It turned out to be fairly straightforward to port the code from QtScript to Ruby. Now all the overloaded method resolution code is written in C++ which makes it much easier to port to other language bindings projects. In the previous version of QtRuby it was half written in C++ and half in Ruby, and needed the equivalent of something like a PhD in 'bindings-ology' to manage to understand it.

QtScript has nice integration with the Qt meta type system. There is a qscriptvalue_cast() function which converts a QtScript instance to the equivalent C++ value whose type is specified via a template. Another function called 'QScriptEngine::toScriptValue() (or qScriptValueFromValue() for MSVC 6) and they go the other way by taking a C++ value and returning the QtScript value. You make the types you want to use with these functions known to QMetaType with qScriptRegisterMetaType() and the conversions 'just work'. You can use the same idea to convert lists of items to and from QtScript by registering those types with qScriptRegisterSequenceMetaType().

For JSmoke I extended this idea so that you could register types for sequences of QtScript values that were wrapped as instances via the Smoke libraries. I also added support for Hashes, Maps and sequences of QPairs. This QMetaType based marshalling was then integrated into the way that Smoke based bindings do marshalling, and meant that everything was tidily implemented via template code. Here is an example of what it looks like in practice:


Q_DECLARE_METATYPE(QDateTime) ...

qScriptSmokeRegisterSequenceMetaType<QList >(engine);
...

Marshall::TypeHandler QtCoreHandlers[] = { { "bool*", marshall_PrimitiveRef }, ...

{ "QList<QDateTime>", marshall_Container<QList<QDateTime> > },                                                                                                  
...

};

The 'QtCoreHandlers' array contains a string with the name of the type, and a template function called 'marshall_Container()' which works with any of the types that you have registered. I think that code like this could easily be automatically generated directly by introspecting the Smoke libraries.

I was able to implement exactly the same thing for QtRuby and instead of qScriptSmokeRegisterSequenceMetaType() above, you would use qRubySmokeRegisterSequenceMetaType(). As well as being a tidy way to implement the marshalling for a Smoke library, it also means that if you are writing C++ to interface with QtRuby you can use qrubyvalue_cast() and qRubyValueToValue() to easily convert between the languages in your glue code.

There is much still to do, and of course help is very welcome. I'm hoping that we can get the new version of QtRuby working well enough in time for KDE 4.7, but we'll have to see how it goes..