GObject is part of the GLib library that implements object-oriented extensions for pure C. This concept, in addition to GLib itself, is used in projects such as GStreamer, GSettings, ATK, Pango, and the entire GNOME project as well as in a large number of applications: GIMP, Inkscape, Geany, Gedit, and many others. A large number of programming languages, starting from mainstream languages ​​such as Python and Java, and ending with delights like Haskell or D, are tied to GLib / GTK +, and for a significant number of languages, binding to GTK + is the only way to build a GUI.
Unlike other similar projects, GObject is distinguished by architectural features whose purpose is to easily and transparently implement library bindings written using pure C and GObject to other programming languages, including dynamic typing and memory management using garbage collection. This explains some of the feeling of over-complication that a programmer may have when he begins to get acquainted with the GObject API. However, this system is very well thought out and logical, so there should be no problems with understanding the whole of the following from a programmer who is familiar with C ++ or Java.
This article illustrates the very basics of working with the GLib object type system.

All cycle about GObject:
GObject Basics
GObject: Inheritance and InterfacesGObject: encapsulation, instantiation, introspection')
At the most basic level of the GObject type system is the GType system, which implements the description of all possible types that the programmer operates at run-time. This system acts as a kind of “glue”, linking C code with code in other languages ​​that are linked to libraries built on the use of the dynamic GLib type system.
The GObject type system supports single inheritance (and taking into account the concept of Java-like interfaces, and more complex conceptual solutions), encapsulation, virtual functions, so-called properties similar to the C ++ fields, but with more system of signals, allowing to create effective and developed constructions within the framework of event-oriented paradigm.
The GObject type system consists of three main entities - fundamental non-storable types like gchar (similar to char from pure C), gpointer (similar to type pointer void), gboolean, and so on; the main purpose of redefining these types is unification and portability; instantiable classifiable types, in general terms similar to C ++ classes; and non-instantiable classified types — interfaces like Java interfaces or purely abstract C ++ classes.
Let's try to write a simple example that demonstrates the work of the GObject library. To get started, let's get acquainted with the main agreements adopted among developers using GLib. The name of any object has a generic name - such a namespace, and specific. For example, all objects present in the GLib library have the prefix “G”, GTK + objects the prefix “Gtk”. The names of the objects are written in the "camel" notation, and the names of the function-methods related to these objects in the "snake". Macros are traditionally written in uppercase, words are separated by underscores. For example, AnimalCat is a Cat object that belongs to Animal's “namespace”, and its “methods” will look like animal_cat_say_meow ().
Let's create two files describing our new catobject - animalcat.c and animalcat.h. Add a re-enable protection to the header file and enable the GObject library:
#ifndef _ANIMAL_CAT_H_ #define _ANIMAL_CAT_H_ #include <glib-object.h>
Add two traditional macros that are used in GLib for compatibility with C ++ compilers and close the protection against re-enabling:
G_BEGIN_DECLS G_END_DECLS #endif /* _ANIMAL_CAT_H_ */
Add the two most important definitions that will be needed in our header file. The first macro is:
#define OURNAMESPACE_TYPE_OUROBJECT ournamespace_ourobject_get_type()
that is, in our particular case:
#define ANIMAL_TYPE_CAT animal_cat_get_type()
This function returns a GType structure containing all the basic type information — the name, the amount of memory required by the object, references to the initialization and finalization functions of the class and object (analogs of C ++ constructors and destructors), etc.
The second macro in general view looks like this:
G_DECARE_DERIVABLE_TYPE (NamespaceObject, namespace_object, NAMESPACE, OBJECT, ParentClass)
in our case:
G_DECLARE_DERIVABLE_TYPE (AnimalCat, animal_cat, ANIMAL, CAT, GObject)
This macro definition is decomposed into a whole set of important macros that perform type conversion, checking for belonging to a specific type, etc. In the last argument, the GObject is declared as the parent class, from which all objects of the GObject type system are inherited.
Essentially, any GObject or object inherited from it consists of two structures: _NamespaceObject and _NamespaceObjectClass, in our case:
struct _AnimalCat struct _AnimalCatClass
We agree to call them the actual object and class. In current versions of GObject, in general, there is no need to implement the structure of the object in an explicit form, it is often generated automatically as a result of the macro expansion. A class must be implemented explicitly if our goal is to build an inheritance hierarchy with overriding virtual functions.
GObjects are of two types that differ in the possibility of inheritance - derivable and final. In the second case, the last macro would look like G_DECLARE_FINAL_TYPE, and in the _NamespaceObject structure would have to be declared explicitly in the .c file. In the case of a derivable-object, you do not need to declare this structure, it will be generated automatically when the macro is expanded.
We describe the _AnimalCatClass structure. This structure exists in a single copy, it is created when creating the first instance of our object, and is destroyed after the destruction of the last instance. As the first field, it should contain a similar structure of the parent class, in our case GObject, since we inherit directly from it. After that, there are other fields, mainly pointers to functions that implement the functionality of virtual methods, as well as fields similar to the static-fields of C ++ classes.
For example, the _AnimalCatClass class might look like this:
struct _AnimalCatClass { GObjectClass parent_class; void (*say_meow)(AnimalCat*); };
Let's add the header file with specific method declarations. Objects inherited from GObject are created by code that, in its simplest form, looks like this:
g_object_new(NAMESPACE_TYPE_OBJECT, NULL);
Such code is usually wrapped in a function of the form:
AnimalCat* animal_cat_new();
Finish the header file with a specific method declaration:
void animal_cat_say_meow(AnimalCat* self);
As we can see, unlike languages ​​like C ++, in this case we need to explicitly pass a pointer to a specific instance.
So, the header file now looks like this:
#ifndef _ANIMAL_CAT_H_ #define _ANIMAL_CAT_H_ #include <glib-object.h> G_BEGIN_DECLS #define ANIMAL_TYPE_CAT animal_cat_get_type() G_DECLARE_DERIVABLE_TYPE (AnimalCat, animal_cat, ANIMAL, CAT, GObject) struct _AnimalCatClass { GObjectClass parent_class; void (*say_meow) (AnimalCat*); }; AnimalCat* animal_cat_new(); void animal_cat_say_meow(AnimalCat* self); G_END_DECLS #endif /* _ANIMAL_CAT_H_ */
Let's go to the file with the actual source code. We include our header file, as well as stdio.h, which is necessary for meowing our sample.
#include <stdio.h> #include "animalcat.h"
It is time for another important macro, which this time has the following form:
G_DEFINE_TYPE (NamespaceObject, namespace_object, G_TYPE_OBJECT)
The last argument that we pass to the macro is similar to the one we defined at the beginning of our header file, which is relevant for the parent object. For our cooobject it should look like ANIMAL_TYPE_CAT, but in this case we use a similar macro of our parent object: G_TYPE_OBJECT. In relation to our situation, this line should look like this:
G_DEFINE_TYPE (AnimalCat, animal_cat, G_TYPE_OBJECT)
Remember, a little earlier we talked about the difference between derivable and final-gobject? If we define a final object that does not imply the possibility of inheritance from it, then we would have to define the structure
struct _NamespaceObject { ParentObject parent; };
which must contain at least one field (and certainly the first) - a similar structure of the parent object. Such mandatory requirements are related to the fact that the address of a pointer to the structure of the parent object must be identical to the addresses of pointers to the structures of all objects inherited from it.
In our case, the structure would look at least like this:
struct _AnimalCat { GObject parent; };
Let me remind you once again - it is necessary to define this structure explicitly only if you use a final type GObject, and we omit this definition in our example.
We proceed directly to the executable code.
We define the animal_cat_say_meow () function to demonstrate how the mechanism of virtual functions works in GObject (you will see later why this is necessary).
static void animal_cat_real_say_meow(AnimalCat* self) { printf("Cat say: MEOW!\n"); }
We also define the actually called method that was declared in the header file:
void animal_cat_say_meow(AnimalCat* self) { AnimalCatClass* klass = ANIMAL_CAT_GET_CLASS (self); klass->say_meow(self); }
The following two important functions are called automatically - the first when creating the first instance of a given object, the second - when creating any particular instance, thus being in a certain sense an analogue of the C ++ constructor.
static void animal_cat_class_init(AnimalCatClass* self) { printf("First instance of AnimalCat was created\n"); self->say_meow = animal_cat_real_say_meow; }
As you can see, we have redefined the “virtual” function defined in the structure-class of our object.
What happens in the code above? In the animal_cat_class_init () function, when initializing the class structure of our object, the link to the say_meow function is assigned the address of the animal_cat_real_say_meow () function; the address of which is currently assigned to the say_meow field in the AnimalCatClass structure. In inheritance objects, we can override this behavior in the corresponding function, whose name ends with class_init.
Let's proceed to the "constructor":
static void animal_cat_init(AnimalCat* self) { printf("Kitty was born.\n"); }
Make a wrapper function that will return a pointer to a new instance of an AnimalCat object:
AnimalCat* animal_cat_new() { return g_object_new(ANIMAL_TYPE_CAT, NULL); }
We write a simple main () function to test the performance of our code:
#include «animalcat.h" int main(int argc, char** argv) { AnimalCat* cat_a = animal_cat_new(); AnimalCat* cat_b = animal_cat_new(); animal_cat_say_meow(cat_a); return 0; }
and Makefile:
CFLAGS = -Wall -g `pkg-config --cflags glib-2.0 gobject-2.0` LDFLAGS = `pkg-config --libs glib-2.0 gobject-2.0` EXEC = kitty SRC = main.c animalcat.c animalcat.c OBJ = main.o animalcat.o animalcat.o $(EXEC): $(OBJ) $(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) %.o: %.c $(CC) -c -o $@ $< $(CFLAGS) .PHONY: clean clean: rm -f $(OBJ) $(EXEC)
We collect and run:
make ./kitty
First instance of AnimalCat was created. Kitty was born. Kitty was born. Cat say: MEOW!