Writing GObjects in C++
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
.
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
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.
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.
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.
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.
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.
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 GType
s 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
).
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.
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.
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.
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.