GObject to Qt dynamic bindings
A couple of years ago I started on a project to create a Qt language binding using the Gnome GObject Introspection libraries to generate QMetaObjects, so that it would be possible to base a language binding on a dynamic bridge between the two toolkits. I started a project in the KDE playground repo, and then Norbert Frese joined in with a companion project called go-consume that was based more on static C++ code generation. I wrote some blogs about how the QMetaObjects creation worked; Creating QMetaObjects from GObject Introspection data, QMetaObject/GObject-introspection inter-operability progress and QMetaObject::newInstance() in Qt 4.5.
We were hoping to give a talk at the Gran Canaria Summit about GTK bindings for Qt, but it didn't get accepted. At the time, I was so busy doing other things that I never managed to follow through and complete the bindings. So the project had languished for the past couple years. Recently I've got going with it again and the project is now being actively developed on Launchpad as smoke-gobject.
I went to the recent Ubuntu UDS conference in Budapest, which was great. The were loads of talks, meetings and other events and I was amazed that Canonical and the Ubuntu community apparently manage to put on an event of this size every six months.
My connection with Ubuntu was that I had been doing some work on fixing bugs with the Unity-2d desktop shell, and had made a start with understanding the code. That project is written in Qt C++ with a lot of QML too. What I found interesting was that it also used Gnome libraries and needed to wrap them in a more Qt-developer friendly Qt/C++ layer. That made me think of the bindings project I never finished a couple of years ago. I discussed doing a binding with the Unity-2d guys at UDS, and they seemed keen on the idea. There are two desktop shell projects for Ubuntu, one called 'Unity-2d' which is the Qt C++/QML one, and a pure Gnome project called 'Unity-3d' which is similar but has more advanced graphics requirements. The Ubuntu guys wanted to create a library that would be written using Gnome apis that could be shared by both Unity-2d and Unity-3d. So it sounded like a perfect test project to see if an automatically generated binding would be possible.
It is now possible to create instances, call instance methods, call methods that are in a namespace, get and set Qt properties that map on to GObject properties, connect to slots and signals in the Qt manner. The marshalling code is pretty complete, although the GObject Introspection marshalling options are pretty large and complex, and it has taken a fair bit of time to get it working.
All that stuff happens inside the QMetaObjects via arcane methods such as QObject::qt_metacall(), and it isn't very easy to write about 'black boxes' of code that do all this exotic stuff. Just recently though, I have finally got as far as the C++ code generation and there is finally something I can point to and describe, that makes it relatively easy to follow what the project is about.
So on principle of 'a code snippet is worth a thousand words', here is a sample of what you get if you run the GObject Introspection description for 'Gtk' through the smoke-gobject runtime. This is the header for the Gtk::Button class:
To me it doesn't look bad - you have some understandable camel case method names, slot, signals and properties that all do what you would expect them to do. There are a couple of problems with this particular code snippet that need sorting out.
Firstly, notice that there are two constructors with exactly the same arguments, and that wouldn't compile. This is because in the underlying library there are two constructor functions for Gtk::Button that have the same arguments; new_with_label() and new_with_mnemonic() both taking a 'gchar*' utf8 argument. How is a bindings author supposed to sort that out? I'm not sure yet. Certainly many languages like Ruby or Python will have the same issue where the constructors are named after the class instances they construct.
A second problem is with enclosing classes in namespaces like 'Gtk::' or 'GObject::' where there are already C structs called the same thing. So I could call them something like 'Qt::Gtk::Button' or lowercase the namespace to 'gtk::Button' - I haven't decided what to do yet.
The generated code for the .cpp part of the Gtk::Button class looks like this:
If you're familiar with code generated by the moc tool, it should look pretty similar. However, with the standard moc, a qt_metacall() function is generated which calls all the slots and properties in the class via a big case statement. Instead in the code above, each slot calls qt_metacall() - ie it works in reverse. No code needs to be generated for the signals and properties as that is all handled by the smoke-gobject runtime.
There is plenty for scope for optimization such as calling the GObject C functions directly where the marshalling is pretty simple. So I think in the long term in can be made efficient although the first version might be slow. I haven't adding virtual method overrides yet, but that shouldn't be too hard as all the info to generate the code can be got from the G-I typelibs.
I you want to checkout and build the project you will need have a GObject Introspection and GObject/Gtk development environment. It is built with cmake, and so it should be just a matter of creating a 'build' directory in the project and typing 'cmake ..' in there. There is a test for the runtime that uses a library that is part of G-I called 'libeverything' and is intended to be a torture test for bindings authors to use to test their code. In the initTestCase() method in tests/everything/tst_everything.cpp you will see this:
It will generate the .h/.cpp sources for the Everything namespace. If you want to have a look at what it does with a namespace like Gtk or Gst you can add a 'Smoke::GObjectNamespace * gtkNamespace = new Smoke::GObjectNamespace("Gtk");' line to the above method.
I am going to the Qt Contributor's Summit this week, and one of the topics for discussion is Interoperability with non-Qt code - Should Qt have better interoperability with (GTK+, Boost, ..)?' run by Jeremy Katz. I'm looking forward to getting some feedback..
glibmm an gtkmm (the C++ wrappers to resp glib and gtk) use the Glib and Gtk namespace - you might want to use different ones to avoid potential conflicts in applications using (even indirectly) either of the two and your bindings.
Re: Gtk namespace
OK interesting. I didn't mention it in the blog, but another way of fixing the problem is to ensure that you don't need to include any glib or gtk C headers when you use the smoke-gobject Qt/C++ bindings. I wasn't sure if that was a good idea because it would stop you from using any direct C function calls in applications using the bindings. So for gtkmm, that must mean they assume you don't need access to the C apis directly.
I suppose the issue with gtkmm vs smoke-gobject namespace clashes would be are there any use cases where you would need to combine application code written using both bindings? I don't know really.
Re: Gtk namespace
More than a direct usage of gtkmm, it could be some case of plugin system with a plugin written in gtkmm, or something like that.
I agree, not a very big case, but something to keep into account while the API is still not set yet.
about Qt and gobject-introspection
Tomeu Vizoso: "tried to comment on your blog post but wasn't able to register in blogs.kde.org.
About what to do with constructors, in PyGObject we have resorted to have the default constructor just call g_object_new, with whatever construction time properties the caller wants to set.
The main reason not being name clashes, but rather that ultimately, inorder to create an instance g_object_new needs to be called *and* it needs to have the right GType (of the class being instantiated, not of the specific class that implements the constructor).
About what to do with the foo_new_bar() C constructors, we just treat them as class methods."
Re: about Qt and gobject-introspection
"tried to comment on your blog post but wasn't able to register in blogs.kde.org."
I'm sorry to hear about that. I look forward to getting comments like
yours on a blog, and it's really annoying when the system makes it
hard to do. Lots of people have complained they can't comment on the
kde developers blogs. Perhaps I will add this mail and the original
question to the blog so that at least we can have some public record.
I'll try and find out whether the system can be changed.
"About what to do with constructors, in PyGObject we have resorted to have the default constructor just call g_object_new, with whatever construction time properties the caller wants to set."
"The main reason not being name clashes, but rather that ultimately, inorder to create an instance g_object_new needs to be called *and* it needs to have the right GType (of the class being instantiated, not of the specific class that implements the constructor)."
Ok, I will need to think how that will affects C++. I was assuming
that you would only be calling constructors for the class that
implements the constructor function.
"About what to do with the foo_new_bar() C constructors, we just treat them as class methods."
Yes, I think I will do that in C++ too. I will rename something like
gtk_button_new_from_stock() as a static C++ method called
Gtk::Button::createFromStock(). I don't think
'Gtk::Button::newFromStock()' seems quite right in C++ and
'createFoo()' is more usual.
I think I should subscribe to the G-I mailing list now I've got a few
questions like this one to ask about.