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:
#ifndef GTK_BUTTON_H
#define GTK_BUTTON_H
#include "gtk_bin.h"
namespace Gtk {
class Button : public Gtk::Bin {
Q_OBJECT
Q_PROPERTY(bool focusOnClick)
Q_PROPERTY(Gtk::Widget* image)
Q_PROPERTY(Gtk::PositionType imagePosition)
Q_PROPERTY(QString label)
Q_PROPERTY(Gtk::ReliefStyle relief)
Q_PROPERTY(bool useStock)
Q_PROPERTY(bool useUnderline)
Q_PROPERTY(float xalign)
Q_PROPERTY(float yalign)
public:
Button();
Button(QString label);
Button(QString stock_id);
Button(QString label);
public slots:
void pressed();
void released();
void clicked();
void enter();
void leave();
void setRelief(Gtk::ReliefStyle newstyle);
Gtk::ReliefStyle relief();
void setLabel(QString label);
QString label();
void setUseUnderline(bool use_underline);
bool useUnderline();
void setUseStock(bool use_stock);
bool useStock();
void setFocusOnClick(bool focus_on_click);
bool focusOnClick();
void setAlignment(float xalign, float yalign);
void alignment(float& xalign, float& yalign);
void setImage(Gtk::Widget* image);
Gtk::Widget* image();
void setImagePosition(Gtk::PositionType position);
Gtk::PositionType imagePosition();
Gdk::Window* eventWindow();
signals:
void activate();
void clicked();
void enter();
void leave();
void pressed();
void released();
};
}
#endif // GTK_BUTTON_H
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:
#include "gtk_button.h"
namespace Gtk {
static QMetaObject *_staticMetaObject = 0;
const QMetaObject Button::metaObject() const
{
if (_staticMetaObject == 0)
_staticMetaObject = (QMetaObject) Smoke::Global::findMetaObject("Gtk::Button");
return _staticMetaObject;
}
void Button::qt_metacast(const char _clname)
{
if (!_clname) return 0;
if (!strcmp(_clname, metaObject()->className()))
return static_cast<void>(const_cast< Button>(this));
return Gtk::Bin::qt_metacast(_clname);
}
int Button::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
return Smoke::GObjectProxy::qt_metacall(_c, _id, _a);
}
Button::Button()
{
Button *_r = 0;
void *_a[] = { &_r };
metaObject()->static_metacall(QMetaObject::CreateInstance, 0, _a);
takeIdentity(_r);
}
Button::Button(QString _t1)
{
Button _r = 0;
void _a[] = { &_r, const_cast<void>(reinterpret_cast<const void>(&_t1)) };
metaObject()->static_metacall(QMetaObject::CreateInstance, 1, _a);
takeIdentity(_r);
}
...
void Button::clicked()
{
void *_a[] = { 0 };
qt_metacall(QMetaObject::InvokeMetaMethod, 314, _a);
}
...
void Button::setRelief(Gtk::ReliefStyle _t1)
{
void _a[] = { 0, const_cast<void>(reinterpret_cast<const void*>(&_t1)) };
qt_metacall(QMetaObject::InvokeMetaMethod, 317, _a);
}
...
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:
void tst_Everything::initTestCase()
{
int id = qRegisterMetaTypeSmoke::GObjectProxy*();
Smoke::Global::initialize();
everythingNamespace = new Smoke::GObjectNamespace("Everything");
}
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..