GObject Consume - Qt/C++ binding generator
Motivation
The aim of GObject-Consume is generating C++ bindings for GObject based APIs on the fly, comparable to Javas "ws-consume" which generates stubs for SOAP webservices. Currently it supports Qt/C++ flavour - standard C++ is covered by gtkmm already. C++ in combination with Qt is very popular for developing GUIs, and GObject provides a good framework for writing platform infrastructure APIs. As it's no big deal to use C libraries in C++, an important goal is to make things more convenient, particularly garbage collection, containers and string types.
This project was inspired by Richard Dales blog (http://www.kdedevelopers.org/node/3878) about his Smoke-GObject project "Creating QMetaObjects from GObject Introspection data" and my previous attempt to hand-craft Qt/C++ bindings for GIO - which just seemed to be lots of boring work (but the experience definitely helped here).
Features
- Uses GObjectIntrospection to generate the bindings
- Models the inheritance hierarchy of GObject classes and interfaces with C++ Wrapper classes, but also allows dynamic casting based on the GType runtime information.
- Wrapper classes are "Value-types" which automatically support reference counting of the encapsulated GObjects in the copy-constructor/operator and destructor.
- Exposes Qt types like QString, QList,...
- Forwards Glib signals to the Qt Signal/Slot system
- Allows subclassing GObject Classes in C++ with a class template
The Code-Generator
At the moment the code-generator generates two (huge) files go-out.h and go-out.cpp. Those files can be compiled with your application.
The following command will generate GTK bindings for certain GTK classes and functions, for instance:
go-consume -C Gtk::Window,Gtk::Button,Gtk::init,Gtk::main,Gtk::main_quit /home/me/sources
How it works
The Base classes
All the auto-generated wrapper classes for GObjects and GInterfaces inherit from GObjectReference:
class GObjectReference // (simplified) { public: GObjectReference() : p(0) {} GObjectReference(const GObjectReference &other); // calls g_object_ref() ~GObjectReference(); // calls g_object_unref() // assignment - calls g_object_unref(old) and g_object_ref(copy): GObjectReference &operator=(const GObjectReference &other); template <typename T> bool instanceOf(); // check if cast is possible (using GType runtime information) template <typename T> // dynamic cast (using GType runtime information) T cast(); bool isNull() const; // a null reference? // connect a GLib signal to a Qt slot: // The first time you call this method, it will // attach a SignalHub (which derives from QObject) instance // to the GObject void connectSignal(const char * gSignal, QObject * obj, const char * qSlot); void * p; // pointer to the encapsulated GObject ... };
There are similar base-classes for structs and boxed types.
A simple example
Here is a simple example for reading a file with GIO:
File file = fileNewForUri(QString("http://l10n.kde.org/docs/doc-primer/docprimer.txt")); Error err; InputStream inputStream = file.read(Cancellable(), err); if (inputStream.isNull()) { cout << "cannot open stream: " << err.getMessage() << endl; exit(1); } char buffer[READBUFSIZE]; while (int n = inputStream.read(buffer, READBUFSIZE-1, Cancellable(), err)) { buffer[n] = '\0'; cout << buffer; }
As you can see, the code almost looks like Java or C#, because the wrappers are "Value-types". Garbage collection happens automatically in the destructors of the stack-allocated wrappers.
Dynamic Casting
Because the inputSteam is actually a FileInputStream, you could cast it to the Seekable interface:
Seekable seekable = inputStream.cast<Seekable>(); seekable.seek(fileSize-20, SeekTypeSet, Cancellable(), err);
But you might want to check whether the cast is really possible:
if (inputStream.instanceOf<Seekable>()) { ... }
Signals
GObjectReference provides a method to connect GLib signals to Qt slots. Here is an example for connecting the GtKWindow "destroy" signal:
class MyApp : public QObject { Q_OBJECT public: MyApp() {} virtual ~MyApp() {} public Q_SLOTS: void destroyWindow(const Gtk::Object& sender) { Gtk::mainQuit(); } }; ... MyApp app; Window window = Window::newInstance(WindowTypeToplevel); window.connectSignal("destroy", &app, SLOT(destroyWindow(Gtk::Object)));
Samples
The samples cover:
- reading a file with GIO
- dynamic casting (GIO/FileInputStream/Seekable)
- A simple GTK example (demonstrates connecting signals)
- Another GTK example (subclassing Gtk::Window)
http://websvn.kde.org/trunk/playground/bindings/gobject-consume/samples/
Status
The project is still in pre-alpha stage, but many things like ref-counting, signals, casting, subclassing are already working. Still missing or sketchy:
- Modelling C Callbacks as Qt signals
- Proper handling of structs
- Proper handling of "transfer" information for args and return-values
- Containers
- Reducing the amount of generated code (at the moment everything up the dependency hierarchy from the "subscribed" classes or functions is generated)
- ...
Source-Code
svn co svn://anonsvn.kde.org/home/kde/trunk/playground/bindings/gobject-consume