GObject is a base object. We don’t usually use GObject itself. Because GObject is very simple and not enough to be used by itself in most situations. Instead, we use descendant objects of GObject such as many kinds of GtkWidget. We can rather say such derivability is the most important feature of GObject.
This section describes how to define a child object of GObject.
An example of this section is an object represents a real number. It is not so useful because we have already had double type in C language to represent real numbers. However, I think this example is not so bad to know the technique how to define a child object.
First, you need to know the naming convention. An object name consists of name space and name. For example, “GObject” consists of a name space “G” and a name “Object”. “GtkWidget” consists of a name space “Gtk” and a name “Widget”. Let the name space be “T” and the name be “Double” of the new object. In this tutorial, we use “T” as a name space for all the objects we make.
TDouble is the object name. It is a child object of GObject. It represents a real number and the type of the number is double. It has some useful functions.
When we say “type”, it can be the type in the type system or C language type. For example, GObject is a type name in the type system. And char, int or double is C language types. When the meaning of the word “type” is clear in the context, we just call it “type”. But if it’s ambiguous, we call it “C type” or “type in the type system”.
TDouble object has the class and instance. The C type of the class is TDoubleClass. Its structure is like this:
typedef struct _TDoubleClass TDoubleClass;
struct _TDoubleClass {
;
GObjectClass parent_class};
_TDoubleClass is a C structure tag name and TDoubleClass is “struct _TDoubleClass”. TDoubleClass is a newly created C type.
TDoubleClass doesn’t need its own member.
The C type of the instance of TDouble is TDouble.
typedef struct _TDouble TDouble;
struct _TDouble {
;
GObject parentdouble value;
};
This is similar to the structure of the class.
TDouble has its own member, “value”. It is the value of TDouble instance.
The coding convention above needs to be kept all the time.
The creation process of TDouble type is similar to the one of GObject.
Usually registration is done by convenient macro such as
G_DECLARE_FINAL_TYPE
and G_DEFINE_TYPE
. You
can use G_DEFINE_FINAL_TYPE
for a final type class instead
of G_DEFINE_TYPE
since GLib version 2.70. So you don’t need
to care about registration details. But, in this tutorial, it is
important to understand GObject type system, so I want to show you the
registration without macro, first.
There are two kinds of types, static and dynamic. Static type doesn’t
destroy its class even after all the instances have been destroyed.
Dynamic type destroys its class when the last instance has been
destroyed. The type of GObject is static and its descendant objects’
type is also static. The function g_type_register_static
registers a type of a static object. The following code is extracted
from gtype.h
in the Glib source files.
GType(GType parent_type,
g_type_register_static const gchar *type_name,
const GTypeInfo *info,
); GTypeFlags flags
The parameters above are:
GTypeInfo
structure will
be explained below.Because the type system maintains the parent-child relationship of
the type, g_type_register_static
has a parent type
parameter. And the type system also keeps the information of the type.
After the registration, g_type_register_static
returns the
type of the new object.
GTypeInfo
structure is defined as follows.
typedef struct _GTypeInfo GTypeInfo;
struct _GTypeInfo
{
/* interface types, classed types, instantiated types */
;
guint16 class_size
;
GBaseInitFunc base_init;
GBaseFinalizeFunc base_finalize
/* interface types, classed types, instantiated types */
;
GClassInitFunc class_init;
GClassFinalizeFunc class_finalize;
gconstpointer class_data
/* instantiated types */
;
guint16 instance_size;
guint16 n_preallocs;
GInstanceInitFunc instance_init
/* value handling */
const GTypeValueTable *value_table;
};
This structure needs to be created before the registration.
sizeof (TDoubleClass)
.class_init
member. By
convention, the name is
<name space>_<name>_class_init
, for example,
t_double_class_init
.class_finalize
member.sizeof (TDouble)
.instance_init
member. By
convention, the name is
<name space>_<name>_init
, for example,
t_double_init
.These information is kept by the type system and used when the object is created or destroyed. Class_size and instance_size are used to allocate memory for the class and instance. Class_init and instance_init functions are called when class or instance is initialized.
The C program example3.c
shows how to use
g_type_register_static
.
#include <glib-object.h>
#define T_TYPE_DOUBLE (t_double_get_type ())
typedef struct _TDouble TDouble;
struct _TDouble {
GObject parent;
double value;
};
typedef struct _TDoubleClass TDoubleClass;
struct _TDoubleClass {
GObjectClass parent_class;
};
static void
t_double_class_init (TDoubleClass *class) {
}
static void
t_double_init (TDouble *self) {
}
GType
t_double_get_type (void) {
static GType type = 0;
GTypeInfo info;
if (type == 0) {
info.class_size = sizeof (TDoubleClass);
info.base_init = NULL;
info.base_finalize = NULL;
info.class_init = (GClassInitFunc) t_double_class_init;
info.class_finalize = NULL;
info.class_data = NULL;
info.instance_size = sizeof (TDouble);
info.n_preallocs = 0;
info.instance_init = (GInstanceInitFunc) t_double_init;
info.value_table = NULL;
type = g_type_register_static (G_TYPE_OBJECT, "TDouble", &info, 0);
}
return type;
}
int
main (int argc, char **argv) {
GType dtype;
TDouble *d;
dtype = t_double_get_type (); /* or dtype = T_TYPE_DOUBLE */
if (dtype)
g_print ("Registration was a success. The type is %lx.\n", dtype);
else
g_print ("Registration failed.\n");
d = g_object_new (T_TYPE_DOUBLE, NULL);
if (d)
g_print ("Instantiation was a success. The instance address is %p.\n", d);
else
g_print ("Instantiation failed.\n");
g_object_unref (d); /* Releases the object d. */
return 0;
}
class
points the
class structure and the argument self
points the instance
structure. They do nothing here but they are necessary for the
registration.t_double_get_type
function. This function
returns the type of the TDouble object. The name of a function is always
<name space>_<name>_get_type
. And a macro
<NAME_SPACE>_TYPE_<NAME>
(all characters are
upper case) is replaced by this function. Look at line 3.
T_TYPE_DOUBLE
is a macro replaced by
t_double_get_type ()
. This function has a static variable
type
to keep the type of the object. At the first call of
this function, type
is zero. Then it calls
g_type_register_static
to register the object to the type
system. At the second or subsequent call, the function just returns
type
, because the static variable type
has
been assigned non-zero value by g_type_register_static
and
it keeps the value.info
structure and calls
g_type_register_static
.g_object_new
is used to instantiate the
object. The GObject API reference says that the function returns a
pointer to a GObject instance but it actually returns a gpointer.
Gpointer is the same as void *
and it can be assigned to a
pointer that points any type. So, the statement
d = g_object_new (T_TYPE_DOUBLE, NULL);
is correct. If the
function g_object_new
returned GObject *
, it
would be necessary to cast the returned pointer. After the creation, it
shows the address of the instance. Finally, the instance is released and
destroyed with the function g_object_unref
.example3.c
is in the src/misc directory.
Execute it.
$ cd src/misc; _build/example3
Registration was a success. The type is 56414f164880.
Instantiation was a success. The instance address is 0x56414f167010.
The registration above is always done with the same algorithm.
Therefore, it can be defined as a macro such as
G_DEFINE_TYPE
.
G_DEFINE_TYPE
does the following:
<name space>_<name>_class_init
. For example, if
the object name is TDouble
, it is
t_double_class_init
. This is a declaration, not a
definition. You need to define it.<name space>_<name>_init
. For example, if the
object name is TDouble
, it is t_double_init
.
This is a declaration, not a definition. You need to define it.<name space>_<name>_parent_class
. For example,
if the object name is TDouble
, it is
t_double_parent_class
.<name space>_<name>_get_type ()
function. For example, if the object name is TDouble
, it is
t_double_get_type
. The registration is done in this
function like the previous subsection.Using this macro reduces lines of the program. See the following
sample example4.c
which works the same as
example3.c
.
#include <glib-object.h>
#define T_TYPE_DOUBLE (t_double_get_type ())
typedef struct _TDouble TDouble;
struct _TDouble {
GObject parent;
double value;
};
typedef struct _TDoubleClass TDoubleClass;
struct _TDoubleClass {
GObjectClass parent_class;
};
G_DEFINE_TYPE (TDouble, t_double, G_TYPE_OBJECT)
static void
t_double_class_init (TDoubleClass *class) {
}
static void
t_double_init (TDouble *self) {
}
int
main (int argc, char **argv) {
GType dtype;
TDouble *d;
dtype = t_double_get_type (); /* or dtype = T_TYPE_DOUBLE */
if (dtype)
g_print ("Registration was a success. The type is %lx.\n", dtype);
else
g_print ("Registration failed.\n");
d = g_object_new (T_TYPE_DOUBLE, NULL);
if (d)
g_print ("Instantiation was a success. The instance address is %p.\n", d);
else
g_print ("Instantiation failed.\n");
g_object_unref (d);
return 0;
}
Thanks to G_DEFINE_TYPE
, we are freed from writing
bothersome code like GTypeInfo
and
g_type_register_static
. One important thing to be careful
is to follow the convention of the naming of init functions.
Execute it.
$ cd src/misc; _build/example4
Registration was a success. The type is 564b4ff708a0.
Instantiation was a success. The instance address is 0x564b4ff71400.
You can use G_DEFINE_FINAL_TYPE
instead of
G_DEFINE_TYPE
for final type classes since GLib version
2.70.
Another useful macro is G_DECLARE_FINAL_TYPE
macro. This
macro can be used for a final type. A final type doesn’t have any
children. If a type has children, it is a derivable type. If you want to
define a derivable type object, use
G_DECLARE_DERIVABLE_TYPE
instead. However, you probably
want to write final type objects in most cases.
G_DECLARE_FINAL_TYPE
does the following:
<name space>_<name>_get_type ()
function. This is only declaration. You need to define it. But you can
use G_DEFINE_TYPE
, its expansion includes the definition of
the function. So, you actually don’t need to write the definition by
yourself.TDouble
, then
typedef struct _TDouble TDouble
is included in the
expansion. But you need to define the structure
struct _TDouble
by yourself before
G_DEFINE_TYPE
.<NAME SPACE>_<NAME>
macro is defined. For
example, if the object is TDouble
the macro is
T_DOUBLE
. It will be expanded to a function which casts the
argument to the pointer to the object. For example,
T_DOUBLE (obj)
casts the type of obj
to
TDouble *
.<NAME SPACE>_IS_<NAME>
macro is defined.
For example, if the object is TDouble
the macro is
T_IS_DOUBLE
. It will be expanded to a function which checks
if the argument points the instance of TDouble
. It returns
true if the argument points a descendant of TDouble
.example4.c
.You need to write the macro definition of the type of the object
before G_DECLARE_FINAL_TYPE
. For example, if the object is
TDouble
, then
#define T_TYPE_DOUBLE (t_double_get_type ())
needs to be defined before G_DECLARE_FINAL_TYPE
.
The C file example5.c
uses this macro. It works like
example3.c
or example4.c
.
#include <glib-object.h>
#define T_TYPE_DOUBLE (t_double_get_type ())
G_DECLARE_FINAL_TYPE (TDouble, t_double, T, DOUBLE, GObject)
struct _TDouble {
GObject parent;
double value;
};
G_DEFINE_TYPE (TDouble, t_double, G_TYPE_OBJECT)
static void
t_double_class_init (TDoubleClass *class) {
}
static void
t_double_init (TDouble *self) {
}
int
main (int argc, char **argv) {
GType dtype;
TDouble *d;
dtype = t_double_get_type (); /* or dtype = T_TYPE_DOUBLE */
if (dtype)
g_print ("Registration was a success. The type is %lx.\n", dtype);
else
g_print ("Registration failed.\n");
d = g_object_new (T_TYPE_DOUBLE, NULL);
if (d)
g_print ("Instantiation was a success. The instance address is %p.\n", d);
else
g_print ("Instantiation failed.\n");
if (T_IS_DOUBLE (d))
g_print ("d is TDouble instance.\n");
else
g_print ("d is not TDouble instance.\n");
if (G_IS_OBJECT (d))
g_print ("d is GObject instance.\n");
else
g_print ("d is not GObject instance.\n");
g_object_unref (d);
return 0;
}
Execute it.
$ cd src/misc; _build/example5
Registration was a success. The type is 5560b4cf58a0.
Instantiation was a success. The instance address is 0x5560b4cf6400.
d is TDouble instance.
d is GObject instance.
Now it’s time to separate the contents into three files,
main.c
, tdouble.h
and tdouble.c
.
An object is defined by two files, a header file and C source file.
tdouble.h
#pragma once
#include <glib-object.h>
#define T_TYPE_DOUBLE (t_double_get_type ())
G_DECLARE_FINAL_TYPE (TDouble, t_double, T, DOUBLE, GObject)
gboolean
t_double_get_value (TDouble *self, double *value);
void
t_double_set_value (TDouble *self, double value);
TDouble *
t_double_new (double value);
#pragma once
prevent the compiler from
reading the header file two times or more. It is not officially defined
but is supported widely in many compilers.T_TYPE_DOUBLE
is public.
G_DECLARE_FINAL_TYPE
is expanded to public
definitions.tdouble.c
#include "tdouble.h"
struct _TDouble {
GObject parent;
double value;
};
G_DEFINE_TYPE (TDouble, t_double, G_TYPE_OBJECT)
static void
t_double_class_init (TDoubleClass *class) {
}
static void
t_double_init (TDouble *self) {
}
gboolean
t_double_get_value (TDouble *self, double *value) {
g_return_val_if_fail (T_IS_DOUBLE (self), FALSE);
*value = self->value;
return TRUE;
}
void
t_double_set_value (TDouble *self, double value) {
g_return_if_fail (T_IS_DOUBLE (self));
self->value = value;
}
TDouble *
t_double_new (double value) {
TDouble *d;
d = g_object_new (T_TYPE_DOUBLE, NULL);
d->value = value;
return d;
}
G_DECLARE_FINAL_TYPE
macro emits
typedef struct _TDouble TDouble
, the tag name of the
structure must be _TDouble
.G_DEFINE_TYPE
macro.value
is the pointer to a
double type variable. Assigns the object value
(self->value
) to the variable. If it succeeds, it
returns TRUE. The function g_return_val_if_fail
is used to
check the argument type. If the argument self
is not
TDouble type, it outputs error to the log and immediately returns FALSE.
This function is used to report a programmer’s error. You shouldn’t use
it for a runtime error. See Glib API Reference
– Error Reporting for further information. The function
g_return_val_if_fail
isn’t used in static class functions,
which are private, because static functions are called only from
functions in the same file and the caller knows the type of
parameters.g_return_if_fail
is used to
check the argument type. This function doesn’t return any value. Because
the type of t_double_set_value
is void
so no
value will be returned. Therefore, we use g_return_if_fail
instead of g_return_val_if_fail
.value
to set the value of the object.g_object_new
to instantiate the
object. The argument T_TYPE_DOUBLE
is expanded to a
function t_double_get_type ()
. If this is the first call
for t_double_get_type
, the type registration will be
carried out.main.c
#include <glib-object.h>
#include "tdouble.h"
int
main (int argc, char **argv) {
TDouble *d;
double value;
d = t_double_new (10.0);
if (t_double_get_value (d, &value))
g_print ("t_double_get_value succesfully assigned %lf to value.\n", value);
else
g_print ("t_double_get_value failed.\n");
t_double_set_value (d, -20.0);
g_print ("Now, set d (tDouble object) with %lf.\n", -20.0);
if (t_double_get_value (d, &value))
g_print ("t_double_get_value succesfully assigned %lf to value.\n", value);
else
g_print ("t_double_get_value failed.\n");
g_object_unref (d);
return 0;
}
tdouble.h
. This is necessary for accessing
TDouble object.d
to point the
object.d
.The source files are located in src/tdouble1. Change your current directory to the directory above and type the following.
$ cd src/tdouble1
$ meson setup _build
$ ninja -C _build
Then, execute the program.
$ cd src/tdouble1; _build/example6
t_double_get_value succesfully assigned 10.000000 to value.
Now, set d (tDouble object) with -20.000000.
t_double_get_value succesfully assigned -20.000000 to value.
This example is very simple. But any object has header file and C source file like this. And they follow the convention. You probably aware of the importance of the convention. For the further information refer to GObject API Reference – Conventions.
Functions of objects are open to other objects. They are like public methods in object oriented languages. They are actually called “instance method” in the GObject API Reference.
It is natural to add calculation operators to TDouble objects because
they represent real numbers. For example, t_double_add
adds
the value of the instance and another instance. Then it creates a new
TDouble instance which has a value of the sum of them.
*
TDouble (TDouble *self, TDouble *other) {
t_double_add (T_IS_DOUBLE (self), NULL);
g_return_val_if_fail (T_IS_DOUBLE (other), NULL);
g_return_val_if_fail double value;
if (! t_double_get_value (other, &value))
return NULL;
return t_double_new (self->value + value);
}
The first argument self
is the instance the function
belongs to. The second argument other
is another TDouble
instance.
The value of self
can be accessed by
self->value
, but don’t use other->value
to get the value of other
. Use a function
t_double_get_value
instead. Because self
is an
instance out of other
. Generally, the structure of an
object isn’t open to other objects. When an object A access to another
object B, A must use a public function provided by B.
Write functions of TDouble object for subtraction, multiplication,
division and sign changing (unary minus). Compare your program to
tdouble.c
in src/tdouble2 directory.