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

Projects/GObjectIntrospection/GObjectConsume (last edited 2013-11-22 19:41:56 by WilliamJonMcCann)