In the last post I discussed about how glibmm, the wrapper of the GLib library exposes GObjects and we finished about a rationale about why one would want to write full-fledged GObjects in C++.

Today we are exploring this venue and observing some of the pain points we are going to face.

Quick recap

GLib is the foundational library on which other technologies like the GTK GUI toolkit or many components of the GNOME Desktop environment software stack build upon. GLib contains GObject, a dynamic type system that implements a more or less classical OOP paradigm. GLib is written in C and glibmm is the C++ wrapper of GLib.

GObject type system exposes classes and instances (objects) of classes as normal C data. Mostly for ergonomic reasons, glibmm focuses on the (GObject) instances and does not expose as much the (GObject) classes. This means that our C++ classes will be used to implement behaviour of (GObject) instances and not so much behaviour of (GObject) classes.

We need a full fledged GObject if we want it to interact with other components in the GTK/GNOME Desktop stack. In particular I’m interested in being able to use those C++-written GObjects in .ui files that describe interfaces.

Current approach

Let’s see a simplified version of the example in the gtkmm book on how to use using derived widgets and .ui files.

First lets define a very simple interface made up of an application window that includes a box container which has our derived button.

derived.ui
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<interface>
  <object class="GtkApplicationWindow" id="WindowDerived">
    <property name="can_focus">False</property>
    <property name="title" translatable="yes">Derived Builder example</property>
    <property name="default_width">150</property>
    <property name="default_height">100</property>
    <property name="hide_on_close">True</property>
    <child>
      <object class="GtkBox" id="dialog-vbox2">
        <property name="orientation">vertical</property>
        <property name="valign">center</property>
        <child type="end">
          <object class="gtkmm__CustomObject_MyButton" id="quit_button">
            <property name="halign">center</property>
            <property name="label">Quit</property>
            <property name="button-ustring">Button with extra properties</property>
            <property name="button-int">85</property>
          </object>
        </child>
      </object>
    </child>
  </object>
</interface>

Line 14 of derived.ui refers to our custom button class. Because it inherits from a Gtk.Button it inherits its properties such as label or halign (which is actually inherited from Gtk.Widget). We will define our own custom properties button-ustring and button-int whose initial values are set to the values in the XML file ("Button with extra properties" and 85, respectively).

Custom button with extra properties

Let’s define now our custom button.

derivedbutton.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef DERIVED_BUTTON_H
#define DERIVED_BUTTON_H

#include <gtkmm.h>

class DerivedButton : public Gtk::Button {
public:
  DerivedButton();
  DerivedButton(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &);
  virtual ~DerivedButton();

  Glib::PropertyProxy<Glib::ustring> property_ustring() {
    return prop_ustring.get_proxy();
  }
  Glib::PropertyProxy<int> property_int() { return prop_int.get_proxy(); }

private:
  Glib::Property<Glib::ustring> prop_ustring;
  Glib::Property<int> prop_int;

  void on_ustring_changed();
  void on_int_changed();
};

#endif

Here we define our two custom properties and we define proxies for them. Proxies will allow us to connect the signal that is emitted when the property changes.

Constructors at lines 8 and 9 deserve some explanation, but first let’s see the implementation of the class.

derivedbutton.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include "derivedbutton.h"
#include <iostream>

// For creating a dummy object in main.cc.
DerivedButton::DerivedButton()
    : Glib::ObjectBase("MyButton"), prop_ustring(*this, "button-ustring"),
      prop_int(*this, "button-int", 10) {}

void DerivedButton::on_ustring_changed() {
  std::cout << "- ustring property changed! new val " << property_ustring()
            << std::endl;
}

void DerivedButton::on_int_changed() {
  std::cout << "- int property changed! new val " << property_int()
            << std::endl;
}

DerivedButton::DerivedButton(BaseObjectType *cobject,
                             const Glib::RefPtr<Gtk::Builder> &)
    : Glib::ObjectBase("MyButton"), Gtk::Button(cobject),
      prop_ustring(*this, "button-ustring"), prop_int(*this, "button-int", 10) {
  property_ustring().signal_changed().connect(
      sigc::mem_fun(*this, &DerivedButton::on_ustring_changed));
  property_int().signal_changed().connect(
      sigc::mem_fun(*this, &DerivedButton::on_int_changed));
}

DerivedButton::~DerivedButton() {}

The constructor at line 5 is a dummy constructor that we will need later, when initialising the application (or widget library). We need it because GLib distinguishes the registering of a class type in the type system and the instantiation of objects of such type as two different steps. However, glibmm combines both, so we need to make sure the class type exists before we can use it generically from GLib or other libraries using GObject. The only way to do this in glibmm is to instantiate a C++ object of the C++ class wrapping the GObject class.

Unfortunately, this also means that any other constructor needs to behave the same when it comes to registering the class type. So the constructor at line 19 needs to initialise Glib::ObjectBase and the properties in the same way, to avoid unexpected inconsistencies. This constructor also has to propagate the C object (cobject) to the parent constructor. This object has been generically built using generic GObject machinery and so we are actually wrapping an object that already exists (i.e. the GObject instance does not exist because we instantiated the class DerivedButton which is another possible scenario).

Main window

Now let’s look at the main window. This is not a custom widget because we won’t be defining new properties for it. However in C++ we will create a subclass for it as well.

derivedwindow.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef DERIVED_WINDOW_H
#define DERIVED_WINDOW_H

#include "derivedbutton.h"
#include <gtkmm.h>

class DerivedWindow : public Gtk::ApplicationWindow {
public:
  DerivedWindow(BaseObjectType *cobject,
                const Glib::RefPtr<Gtk::Builder> &builder);
  virtual ~DerivedWindow();

protected:
  // Signal handlers:
  void on_button_quit();

  Glib::RefPtr<Gtk::Builder> m_builder;
  DerivedButton *m_pButton;
};

#endif

Line 9 contains a constructor that again, wraps a GObject instance that will be created elsewhere. Parameter builder is a reference to Gtk.Builder which is an object used to create interfaces from .ui files.

derivedwindow.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "derivedwindow.h"
#include <iostream>

DerivedWindow::DerivedWindow(BaseObjectType *cobject,
                             const Glib::RefPtr<Gtk::Builder> &builder)
    : Gtk::ApplicationWindow(cobject), m_builder(builder),
      m_pButton(nullptr) {
  // Get the Gtk.Builder-instantiated Button, and connect a signal handler:
  m_pButton = Gtk::Builder::get_widget_derived<DerivedButton>(m_builder,
                                                              "quit_button");
  if (m_pButton) {
    m_pButton->signal_clicked().connect(
        sigc::mem_fun(*this, &DerivedWindow::on_button_quit));
  }
}

DerivedWindow::~DerivedWindow() {}

void DerivedWindow::on_button_quit() {
  // set_visible(false) will cause Gtk::Application::run() to end.
  set_visible(false);
}

The implementation is pretty straightforward, we wrap the created gobject and we keep a reference to the Gtk.Builder we receive. Then we use the builder instance to obtain our derived button. If all goes well we connect the clicked signal so it hides the dialog. We will use this later to quit the application.

Main application

The only last piece remaining is the entry point to our application.

main.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include "derivedwindow.h"
#include <cstring>
#include <iostream>

namespace {

DerivedWindow *pWindow = nullptr;
Glib::RefPtr<Gtk::Application> app;

void on_app_activate() {
  // Create a dummy instance before the call to refBuilder->add_from_file().
  // This creation registers DerivedButton's class in the GObject type system.
  // This is necessary because DerivedButton contains user-defined properties
  // (Glib::Property) and is created by Gtk::Builder.
  static_cast<void>(DerivedButton());

  // Load the GtkBuilder file and instantiate its widgets:
  auto refBuilder = Gtk::Builder::create();
  try {
    refBuilder->add_from_file("derived.ui");
  } catch (...) {
    std::cerr << "Error while loading .ui file\n";
    return;
  }

  // Get the GtkBuilder-instantiated dialog:
  pWindow = Gtk::Builder::get_widget_derived<DerivedWindow>(refBuilder,
      "WindowDerived");

  if (!pWindow) {
    std::cerr << "Could not get the dialog" << std::endl;
    return;
  }

  // It's not possible to delete widgets after app->run() has returned.
  // Delete the dialog with its child widgets before app->run() returns.
  pWindow->signal_hide().connect([]() { delete pWindow; });

  app->add_window(*pWindow);
  pWindow->set_visible(true);
}
} // anonymous namespace

int main(int argc, char **argv) {
  app = Gtk::Application::create("org.gtkmm.example");

  // Instantiate a dialog when the application has been activated.
  // This can only be done after the application has been registered.
  // It's possible to call app->register_application() explicitly, but
  // usually it's easier to let app->run() do it for you.
  app->signal_activate().connect([]() { on_app_activate(); });

  return app->run(argc, argv);
}

Our program will start its execution at line 45. We create a Gtk::Application with a proper app-id and then we connect the activate signal in line 51. Then we run the application in line 53.

The activation signal is connected to the function on_app_activate at line 10. One first thing it does is to ensure that our custom GObject class type is registered. This class will be called gtkmm__CustomObject_MyButton inside the GObject type system, and this is the name we used above in our XML file. As I mentioned above, because glibmm combines class registration and object instantiation in a single process, we need to create a dummy object (that will be immediately destroyed) before Gtk.Builder instantiates an object of class gtkmm__CustomObject_MyButton. If you remove line 15, line 20 will fail because it will not be able to instantiate our custom GObject class.

The rest is more or less straightforward: we get the window instance from the .ui file and we connect the hide signal so we destroy the window upon returning. Recall that in the constructor of DerivedWindow we made our button to hide the window, so it quits the application. We finally make the window visible.

Discussion

This is the suggested approach in glibmm. I think its bigger advantage is that it does not require a lot of additional machinery. However, due to the way glibmm works internally, we need to remember to create a fake instance that registers our class type in GObject. This requires a dummy default constructor (which might be a problem when extending a class that does not have one) in addition to the usual wrapping constructor used by Gtk::Builder. All the constructors we want to have will have to be synchronised (though C++ can mitigate this thanks to forwarding constructors and non-static data member initialisers).

Let’s see if we can do something a bit more predictable. While the approach used by glibmm is reasonable, registering a class type as a side effect of creating an instance for me breaks the principle of least surprise. In fact, the ability of glibmm to hide the concept of the GObject class is so successful that unless one starts reading glibmm’s code, it may be difficult to understand how all the pieces fit. Leaving a user of the library with that “magic” feeling that suddenly turns to unease when we cannot really explain how it all works.

Manual approach

Let’s follow a more manual approach, inspired by what gmmproc does. gmmproc is the wrapping machinery that can be used to wrap GObject-based libraries. I will do this with the DerivedButton class (though a similar approach can be used with DerivedWindow if wanted).

One big downside of this approach is that we need some amount of boilerplate (which gmmproc does for this when wrapping existing GObject-based libraries).

Custom class helper

We will have to define the GObject class class and the GObject instance class. To define the class we will use a custom class that we will use to sidestep some of the glibmm defaults.

customclass.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef GLIBMM_CUSTOMCLASS_H
#define GLIBMM_CUSTOMCLASS_H

#include <glibmm/class.h>

namespace Glib {
class CustomClass : public Class {
public:
  // Inherit constructors;
  using Class::Class;

  // Reintroduce existing overloads.
  using Class::register_derived_type;
  // Our new overload.
  void register_derived_type(GType base_type,
                             GInstanceInitFunc instance_init = nullptr,
                             const char *type_name = nullptr,
                             GTypeModule *module = nullptr);
};

} // namespace Glib

#endif // GLIBMM_CUSTOMCLASS_H

The implementation class is a bit longer but basically repeats what Glib::Class does but allowing us to specify a name.

customclass.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include "customclass.h"

namespace Glib {

void CustomClass::register_derived_type(GType base_type,
                                        GInstanceInitFunc instance_init,
                                        const char *type_name,
                                        GTypeModule *module) {
  if (gtype_)
    return; // already initialized

  // 0 is not a valid GType.
  // It would lead to a crash later.
  // We allow this, failing silently, to make life easier for gstreamermm.
  if (base_type == 0)
    return; // already initialized

#if GLIB_CHECK_VERSION(2, 70, 0)
  // Don't derive a type if the base type is a final type.
  if (G_TYPE_IS_FINAL(base_type)) {
    gtype_ = base_type;
    return;
  }
#endif

  GTypeQuery base_query = {
      0,
      nullptr,
      0,
      0,
  };
  g_type_query(base_type, &base_query);

  // GTypeQuery::class_size is guint but GTypeInfo::class_size is guint16.
  const guint16 class_size = (guint16)base_query.class_size;

  // GTypeQuery::instance_size is guint but GTypeInfo::instance_size is
  // guint16.
  const guint16 instance_size = (guint16)base_query.instance_size;

  const GTypeInfo derived_info = {
      class_size,
      nullptr,          // base_init
      nullptr,          // base_finalize
      class_init_func_, // Set by the caller ( *_Class::init() ).
      nullptr,          // class_finalize
      nullptr,          // class_data
      instance_size,
      0, // n_preallocs
      instance_init,
      nullptr, // value_table
  };

  if (!(base_query.type_name)) {
    g_critical("Class::register_derived_type(): base_query.type_name is NULL.");
    return;
  }

  gchar *derived_name =
      (type_name && *type_name != '\0')
          ? g_strdup(type_name)
          : g_strconcat("gtkmm__", base_query.type_name, nullptr);

  if (module)
    gtype_ = g_type_module_register_type(module, base_type, derived_name,
                                         &derived_info, GTypeFlags(0));
  else
    gtype_ = g_type_register_static(base_type, derived_name, &derived_info,
                                    GTypeFlags(0));

  g_free(derived_name);
}

} // namespace Glib

Header

With this first piece of boilerplate done, we can focus on manually deriving our button.

derivedbutton.h
1
2
3
4
5
6
7
8
9
10
11
#ifndef GTKMM_EXAMPLE_DERIVED_BUTTON_H
#define GTKMM_EXAMPLE_DERIVED_BUTTON_H

#include "customclass.h"
#include <gtkmm.h>

extern "C" {
// C types
struct ExampleDerivedButton;
struct ExampleDerivedButton_Class;
}

We will first define two opaque types as if they were the original C types for our GObject. We will use those later.

We will first make a forward declaration to the C++ class that represents the GObject class and then we can define the C++ class that represents the GObject instances.

derivedbutton.h
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class DerivedButton_Class;

class DerivedButton : public Gtk::Button {
public:
  DerivedButton(ExampleDerivedButton *object);
  DerivedButton(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &);
  virtual ~DerivedButton();

  static GType get_type();
  static GType get_base_type();

  ExampleDerivedButton *gobj() const {
    return reinterpret_cast<ExampleDerivedButton *>(gobject_);
  }

  Glib::PropertyProxy<Glib::ustring> property_ustring() {
    return Glib::PropertyProxy<Glib::ustring>(this, "button-ustring");
  }
  Glib::PropertyProxy<int> property_int() {
    return Glib::PropertyProxy<int>(this, "button-int");
  }

  static DerivedButton *wrap(GObject *object, bool take_copy = false);

private:
  friend DerivedButton_Class;
  static DerivedButton_Class derived_button_class;

  static void instance_init_function(GTypeInstance *instance, void *g_class);

  void on_ustring_changed();
  void on_int_changed();

  static void set_property(GObject *object, guint property_id,
                           const GValue *value, GParamSpec *pspec);
  static void get_property(GObject *object, guint property_id, GValue *value,
                           GParamSpec *pspec);

  Glib::ustring button_ustring;

  int button_int;
};

Now the class.

derivedbutton.h
71
72
73
74
75
76
77
78
79
80
81
class DerivedButton_Class : public Glib::CustomClass {
private:
public:
  friend class DerivedButton;
  const Glib::Class &init();
  static void class_init_function(void *g_class, void *class_data);

  static Glib::ObjectBase *wrap_new(GObject *object);
};

#endif // GTKMM_EXAMPLE_DERIVED_BUTTON_H

DerivedButton_Class implementation

There is a lot to unpack in the header above. I think, however that it is easier to start from the class DerivedButton_Class. First note the static data member derived_button_class in line 54 of DerivedButton class. This will represent the GObject class and it will be used by DerivedButton to register the type. This happens because we will obtain a reference of a Glib::Class via the DerivedButton_Class::init.

derivedbutton.cc
135
136
137
138
139
140
141
142
143
144
const Glib::Class &DerivedButton_Class::init() {
  if (!gtype_) {
    class_init_func_ = DerivedButton_Class::class_init_function;
    register_derived_type(DerivedButton::get_base_type(),
                          DerivedButton::instance_init_function, "MyButton");
    Glib::init();
    Glib::wrap_register(gtype_, &wrap_new);
  }
  return *this;
}

gtype_ is a data-member inherited from Glib::Class. If zero it means the class needs registration, so we do this. We set DerivedButton_Class::class_init_function as the class initialisation function (field class_init_func_ is also inherited and used in our CustomClass::register_derived_type defined earlier). For simplicity of the implementation, though this could be done better we invoke Glib::init that will initialise all the internal machinery from glibmm and then we link this new type with DerivedButton_Class::wrap_new. Recall that glibmm wraps GObjects with a C++ object so it needs to link both, here we link this type with the creation function. The creation function looks like this

derivedbutton.cc
146
147
148
Glib::ObjectBase *DerivedButton_Class::wrap_new(GObject *object) {
  return new DerivedButton((ExampleDerivedButton *)object);
}

Finally when an object of the class is instantiated for the first time our class initialisation function (DerivedButton_Class::class_init_function) will be invoked. It looks like this.

derivedbutton.cc
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
void DerivedButton_Class::class_init_function(void *g_class, void *class_data) {
  g_print("%s\n", __PRETTY_FUNCTION__);
  auto *const gobject_class = static_cast<GObjectClass *>(g_class);

  gobject_class->get_property = DerivedButton::get_property;
  gobject_class->set_property = DerivedButton::set_property;

  g_object_class_install_property(
      gobject_class, PROPERTY_INT,
      g_param_spec_int(
          "button-int", "", "", G_MININT, G_MAXINT, 0,
          static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_CONSTRUCT)));
  g_object_class_install_property(
      gobject_class, PROPERTY_STRING,
      g_param_spec_string(
          "button-ustring", "", "", "",
          static_cast<GParamFlags>(G_PARAM_READWRITE | G_PARAM_CONSTRUCT)));

  const auto cpp_class = static_cast<Gtk::Button_Class *>(g_class);
  Gtk::Button_Class::class_init_function(cpp_class, class_data);
}

We basically install a couple of properties (using the C API, I don’t think we can do much better here) and then we proceed to initialise the base class, in our case Gtk::Button. PROPERTY_INT and PROPERTY_STRING are a couple of enumerators that we use to identify these properties in this class.

derivedbutton.cc
66
67
68
69
70
enum PropertyId {
  INVALID_PROPERTY,
  PROPERTY_INT,
  PROPERTY_STRING,
};

This completes our implementation of the class. Note that we mention a couple of functons in DerivedButton to access the properties that we have just installed.

DerivedButton implementation

I’m going to list here only the functions that have changes.

derivedbutton.cc
32
33
34
35
36
37
38
39
DerivedButton::DerivedButton(BaseObjectType *cobject,
                             const Glib::RefPtr<Gtk::Builder> &)
    : Gtk::Button(cobject) {
  property_ustring().signal_changed().connect(
      sigc::mem_fun(*this, &DerivedButton::on_ustring_changed));
  property_int().signal_changed().connect(
      sigc::mem_fun(*this, &DerivedButton::on_int_changed));
}

The constructor that can be invoked by the builder is almost the same, it does not have to invoke the constructor of ObjectBase in any special way.

Ideally we would use this constructor, but it turns out that we may build the wrapping C++ object earlier. So let’s add one constructor for this case.

derivedbutton.cc
41
42
43
44
45
46
47
DerivedButton::DerivedButton(ExampleDerivedButton *obj)
    : Gtk::Button((GtkButton *)obj) {
  property_ustring().signal_changed().connect(
      sigc::mem_fun(*this, &DerivedButton::on_ustring_changed));
  property_int().signal_changed().connect(
      sigc::mem_fun(*this, &DerivedButton::on_int_changed));
}

Needless to say that, even if I did not do here, we can factor out the body of the constructor.

One of the functions that GObject requires is an instance initialisation function but ours does not have to do anything special because we will keep the state in the C++ object and not in the GObject itself.

derivedbutton.cc
51
52
53
54
void DerivedButton::instance_init_function(GTypeInstance *instance,
                                           void * /* g_class */) {
  // Does nothing.
}

There are two functions used when registering the GObject class in DerivedButton_Class. Those return GTypes which is the way GObject uses to identify types (they are just integer handles). We need one for the current class (MyButton) and one for the base (GtkButton).

derivedbutton.cc
56
57
58
59
60
GType DerivedButton::get_type() {
  return derived_button_class.init().get_type();
}

GType DerivedButton::get_base_type() { return GTK_TYPE_BUTTON; }

When requesting the curerent type, this will register the type using the init member function of DerivedButton_Class.

Finally we need a function that knows how to wrap a C GObject representing our class (not the C++ one) into a C++ object, creating one if needed. This is done using Glib::wrap_auto. This function will invoke, if there is no C++ wrapper object for the GObject, the function DerivedButton_Class::wrap_new shown earlier and that we registered in glibmm when registering the new GObject class type.

derivedbutton.cc
62
63
64
DerivedButton *DerivedButton::wrap(GObject *object, bool take_copy) {
  return dynamic_cast<DerivedButton *>(Glib::wrap_auto(object, take_copy));
}

I mentioned earlier that we need a couple of functions to access the properties. We still need to implement them. Those functions are basically C interfaces but we can still use most of the time the glibmm wrappers.

derivedbutton.cc
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
void DerivedButton::set_property(GObject *object, guint property_id,
                                 const GValue *value, GParamSpec *pspec) {
  DerivedButton *this_ = DerivedButton::wrap(object);
  g_assert(this_);

  switch (property_id) {
  case PROPERTY_INT: {
    Glib::Value<int> v;
    v.init(value);
    int new_val = v.get();
    if (new_val != this_->button_int) {
      this_->button_int = new_val;
      g_object_notify_by_pspec(object, pspec);
    }
    break;
  }
  case PROPERTY_STRING: {
    Glib::Value<Glib::ustring> v;
    v.init(value);
    Glib::ustring new_val = v.get();
    if (new_val != this_->button_ustring) {
      this_->button_ustring = v.get();
      g_object_notify_by_pspec(object, pspec);
    }
    break;
  }
  default: {
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
    break;
  }
  }
}

void DerivedButton::get_property(GObject *object, guint property_id,
                                 GValue *value, GParamSpec *pspec) {
  DerivedButton *this_ = DerivedButton::wrap(object);
  g_assert(this_);

  switch (property_id) {
  case PROPERTY_INT: {
    Glib::Value<int> v;
    v.init(v.value_type());
    v.set(this_->button_int);
    g_value_copy(v.gobj(), value);
    break;
  }
  case PROPERTY_STRING: {
    Glib::Value<Glib::ustring> v;
    v.init(v.value_type());
    v.set(this_->button_ustring);
    g_value_copy(v.gobj(), value);
    break;
  }
  default: {
    G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
    break;
  }
  }
}

There is an interesting trivia fact here, is that Glib::Property<T> as provided by glibmm installs the properties when creating of the object wrapper while we have installed them when creating the class.

Another difference, is that glibmm’s generic function to get and set properties will always notify about changes even if the property is set to the previous value it held. We show a simple way to implement a more precise mechanism here.

Another interesting fact that happens here, is that the call to DerivedButton::wrap happens while initialising the GObject via Gtk.Builder, this means that we will invoke the new constructor we added and that the previous one we had, will not be invoked because when the DerivedWindow class tries to obtain the derived button, the wrapper object will exist already, so the constructor we had will not actually run.

Registering the type

Finally we need to make sure the type exists. We do that by registering it at the beginning of the application, in the same place were before we had to create a dummy instance instead.

main.cc
28
29
30
31
32
void on_app_activate() {
  // Make sure the type has been registered.
  g_type_ensure(DerivedButton::get_type());
  // ...
}

Discussion

When writing the wrapper manually, we need a moderate amount of boilerplate. In defense of gtkmm, though, the boilerplate is more or less at the level of what one usually needs when implementing GObjects in C. Also a few things cannot be done in C++ (because glibmm does not wrap much on the side of the classes) so we end invoking C interfaces.

One interesting thing we have not addressed are signals, unfortunately signals require the creation of a function that marshalls correctly the parameters. I think some C++ template pixie dust can help here, but the function must exist. Adding new signals is, thus, not trivial.

Finally, one thing that may not be obvious, is that the GObject will always entail the existence of a C++ wrapper. This is a fundamental aspect of glibmm, so while we can implement a full-fledged GObject, it will always require its C++ counterpart around.

Conclusion

Given the seamless integration between C and C++, it is relatively straightforward to fully write a new GObject using C++. The recommended approach in the gtkmm documentation has the downside it requires a default constructor (imposing this requirement to the base class) and creating a dummy object that will cause the registration of the new GObject class.

When written manually, the amount of boilerplace is significant and given that glibmm does not wrap much the C API for classes itself, we find ourselves forced to use GObject C interfaces.

All in all, I believe the recommended approach is more reasonable as long as we understand the nuance with the registration of the derived GObject class.