GObject is the foundational dynamic type system implemented on top of the C language that is used by many other libraries like GLib, GTK and many other components, most of them part of the GNOME desktop environment stack.

I’ve been lately wrapping a C library that uses GObject for C++ and I learned about some of the challenges.

GObject

Any general programming language can be used under the Object Oriented Programming (OOP) paradigm, and the difference between them is whether the language offers built-in support for that or not. So, when we say that Java is OOP we basically mean that the language has concepts which are meant to support this paradigm out of the box.

C is not one of those languages.

For reasons lost in the mist of time, related to the origins of the GNU Image Manipulation Program, the GTK toolkit, a GUI toolkit, was written in C. And its foundations are built on top of a library called GLib. GLib provides GObject: a library based OOP type system built on top of C. GTK and other libraries, part of the GNOME Desktop software stack, are built on top of GObject.

Now, GObject is powerful (just read about it but it also acknowledges the fact that there are more programming languages than just C, even if C serves as the common denominator here.

This is also the current reality: C these days can be seen as an interoperable layer between programming languages. Most foreign-function interfaces (foreign as in “written in another programming language”) target C as the interoperable layer. There are technical reasons for that fact, which are out of scope of this blog.

C++ is not, strictly, a superset of C but it can interoperate with C very, very easily (the C heritage in C++ enables this and also fuels many pain points of C++ itself). And C++, even if it has been dubbed as “multi paradigm”, has reasonable support for OOP.

So it makes sense to provide a C++ interface to GObject.

Wrapping on top of glibmm

GLib is the library that contains GObject and there already exists a C++ version of it called glibmm.

glibmm, along with another component called mm-common, allows systematically wrapping GObject-based C libraries in a consistent and coherent way. This is achieved using a tool called gmmproc. I used this approach for my wrap of libadwaitamm.

There are some design decisions made by glibmm that permeate and impact the wrappers.

Classes and objects

Because GObject is actually a library and implements an OOP type system, all the concepts of such system must exist as entities of the program. When working on a typical OOP language like C++ or Java, the concept of “class” is a concept provided and supported by the language itself.

This is not the case in GObject. Classes are entities represented in the memory of the program like regular data.

In fact when reading the GObject tutorial you will identify lots of steps required to register (or bring up) a class in GObject. GObject programmers identify that some of those steps are annoying and feel like boilerplate. To ease the pain they use C macros so the GObject classes can be declared and defined in a more convenient way.

Toshio Sekiya made this excellent GObject tutorial in C that is worth checking.

Once a class has been registered in GObject, we can instantiate it.

glibmm tries to make the use of GObject instances as convenient as regular C++ objects so it combines the class registration in GObject with the instantiation of a GObject class.

This works most of the time but complicates the process because classes themselves do not have a “constructor” method in C++ (only instances do). These “class constructors” are used to register class-level attributes like signals and properties.

glibmm solves this problem by using a secondary class, which is automatically generated by the wrapping machinery, that represents the class itself. This class object is used as a singleton of the application and it is initialised upon the creation of the first instance of a GObject class. This initialisation can then invoke a function that can register properties, signals and interfaces implementations.

Signals

Signals in GObject are close to what in other programming languages (like C# or Java) are called delegates or listeners. It is possible to connect to a signal so a piece of code, as a callback, is executed when something happens. Signals can be arbitrarily defined by a GObject class so the GObject instance can emit those signals as needed.

glibmm was written in a pre-C++11 world and back then it used the libsigc++ library to ensure type-safety in the callbacks (something that C can’t do and it is sometimes [ab]used by the C libraries). This library is still very useful these days, but in a post-C++11 world some of the heavy lifting can be delegated to the C++ standard library itself.

libsigc++ provides two concepts: signals (something that can be emitted) and slots (something that can be connected to a signal and will be invoked when the signal is emitted). Because libsigc++ is generic and not tied to glibmm (even if it is, maybe, one of its biggest users), the glibmm wrapping machinery has to translate a signal callback (a C callback) into a proper libsigc++ slot. Luckily, almost all callbacks in GObject are closures that receive a void* argument where anything related to the context can be passed to the callback. This way, when wrapping a GObject implemented in C, the wrapping machinery connects the existing (GObject’s) signals to a callback (a free function, typically generated) that unwraps the context pointer into libsigc++’s slots for that libsigc++ signal.

Properties

Many OOP programming languages (like C# or Object Pascal) have the concept of “properties”. They look like object attributes (fields) but can invoke a function when reading or writing the attribute.

GObject properties follow this philosophy and introduce a couple of extra features: properties have a (GObject) signal associated to them that can be used to signal updates to the property and can be generically read and written using GObject generic mechanisms. These two features allow properties to be bound to other properties and build expressive GUIs with reasonable effort.

For instance, if we have a hypothetical list widget with a property number-of-elements, we can bind this property to the sensitive property of a Gtk.Button intended to clear that list widget. This way, we can enable or disable the button based on whether the list widget contains items. More complex scenarios are possible using Gtk.Expression.

Properties are implemented in GObject with two callbacks that are invoked when a property is read or written, respectively.

The challenge of subclassing

Now, if our goal was to only wrap existing GObjects, a scenario that all the machinery of glibmm supports very well, we would be done.

Although the GObject type system allows to introduce new fundamental types (which are mostly meant to represent built-in language types such as int or double), most of the new types defined by a library or application are created by means of subclassing (if indirectly) the GObject.Object class type itself.

Now, subclassing a class in GObject means registering a class and letting the registration procedure know the parent class (GObject, like Java or C# but in contrast to C++, allows only single base class). This process would be burdensome given that the additional class that represents the class is a bit of a pain to write. The glibmm mechanism of a separate class that represents the class entity in GObject is not super convenient to write manually.

So in that line glibmm devised a convenient mechanism in which by using the regular C++ inheritance one could create a new class almost transparently.

Subclassing is magic

Consider that you want to subclass Gtk::Button.

You can just do

class MyButton : public Gtk::Button {
 public:
  MyButton(const Glib::ustring &label) : Gtk::Button(label) {}
  // ...
};

And that’s it. No need for a separate MyButton_Class or the likes that represents the GObject class itself. Cool, but how does this work?

gmmproc-wrapped classes always register a derived class that just clones the original wrapped class. In the case of Gtk::Button, the original C class is GtkButton. The wrapped code registers (just once) a gtkmm__GtkButton class in the GObject typesystem and makes it a subclass of GtkButton. The reason why this is done is in order to allow implementing a virtual method mechanism, explained below.

Note, however, that no class is registered in GObject for MyButton. At the eyes of GObject any instance of MyButton is just a gtkmm__GtkButton.

Virtual methods

GObject would not be a complete OOP mechanism if it did not support polymorphism via virtual table classes. In the C implementation, virtual methods are implemented as pointers to functions and those are overriden explicitly by subclasses in the “class constructor” by setting them to point to specific functions.

Virtual methods are exposed as a convenience in gmmproc-wrapped classes as regular C++ virtual methods. To make this work, however, the class must have had to overriden the GObject virtual method so it ultimately calls the C++ virtual method. This can only happen in the “class constructor”. By subclassing with a wrapper that introduces no extra data, gmmproc-wrapped classes can override GObject virtual methods at will.

This is exactly what happens with Gtk.Button.clicked virtual method. When initialising the class gtkmm__GtkButton this virtual method is made to invoke a C++ virtual method (generated by gmmproc) called on_clicked. If the method is not actually overridden in the subclass, gmmproc calls the current virtual method implementation (if any).

class MyButton : public Gtk::Button {
 public:
  MyButton(const Glib::ustring &label) : Gtk::Button(label) {}

  virtual on_clicked() override {
    // ...
  }
};

Properties

But if we did not create a new GObject class to represent MyButton and we’re just using C++ owns mechanism for virtual methods, what about new signals or properties we might want to add?

This is where this convenient scheme of inheriting, one that does not require a description of the class, starts showing its limits.

First we need to make sure the new class is actually a new one. This can be achieved using a different constructor of Glib::ObjectBase. While the root of the hierarchy is Glib::Object (it wraps GObject.Object), Glib::ObjectBase is a virtual base of Glib::Object that is used to change some of the behaviour when creating Glib::Object. Glib::ObjectBase has a constructor where you can specify a class name.

class MyButton : public Gtk::Button
{
 public:
  MyButton(const Glib::ustring &label) :
    Glib::ObjectBase("MyButton"),
    Gtk::Button(label) {}
  // ...
};

When using this constructor, glibmm will register a new class gtkmm__CustomObject_MyButton. And this allow us to define properties.

class MyButton : public Gtk::Button {
 public:
  MyButton(const Glib::ustring &label)
      : Glib::ObjectBase("MyButton"), Gtk::Button(str) {}

  Glib::Property<int> my_value{*this, "my-value", 0};
};

Now, properties are class-level attributes so ideally those should be registered (installed) in the class constructor, which we cannot access. However, GObject allows installing properties later and this is what happens when executing the constructor of the property my_value that is run as part of the constructor of MyButton.

Signals

What about signals? Unfortunately, as far as I can tell, there is no straightforward way to install new custom GObject signals.

Note that libsigc++ can be used in some signalling scenarios as an alternative to GObject signals. This is because, in contrast to properties, GObject signals do not seem to be composable between them. So we may only need a thing that acts like a wrapped signal even if it is not a proper GObject signal.

If we do want a GObject signal, one thing we can do is using Glib::ExtraClassInit which allows us to define our own class initialisation function. But note that this will be executed the first time we instantiate our class. This fragile (at least to me) behaviour is again part the price we pay for not decoupling the C++ class that represents instances from the C++ class that represents the GObject class itself.

Why would we want to use C++ to write a GObject?

If we look at the wrapper libraries as a mean to write C++, one might think that we only need the minimal wrapping surface and then be able to use C++, outside of GObject, to develop the rest of the functionality.

While I do not think is super essential to be able to write a GObject in C++ so it can be called from outside C++ (this would force us to provide a C interface anyways), I think it is useful to be able to bring up a GObject in C++ so it can be used in some of the convenient machinery that GTK provides: mainly .ui files and Gtk.Builder.

Now, .ui files are very powerful and can do lots of things for us in a convenient way. But this can only happen if the GTK library sees a full-fledged GObject. The class type must have been registered in GObject and its properties, signals and interfaces must have been registered during class initialisationn (not later, like glibmm allows us to do).

And I would like to use C++ to do that, as much as possible. So in a next post I will explore some approaches I have been using in my projects.