In comments to the
previous article, it was often suggested that the GObject system is not needed due to the presence of C ++ and other high-level languages. In addition to purely technical points, which have already been discussed in the comments, I would like to touch on another aspect. Probably the majority of commentators see the rationale for the existence of the GLib object system in the stubborn unwillingness of sichnik-retrogrades to enjoy the benefits of civilization and to accept the inexorable step of progress. Probably the way it was at the dawn of Glib / GTK, originating in the world of UNIX systems, GNU, open-source, Stallman's ideas, etc. Most of that generation of hackers really preferred C, while C ++ was relatively young and undeveloped and the benefits of using it seemed not so obvious.
Today, of course, for new projects, most of us would prefer using more convenient, concise and safe languages, even if we are familiar with all the nuances of using GObject. However, one should not lose sight of the fact that in the more than 20 years of the existence of GLib / GTK, thousands of applications and libraries have been created with their use, many of which are being actively developed today by thousands of programmers from around the world. They add new functionality, catch bugs, adapt them to modern technologies like HiDPI screens, Wayland, Vulkan, etc. In order to read (add, correct) the code of such projects, you need to have basic knowledge of object-oriented extensions for C, which we are talking about.
I beg your mercy under cat. We train, as usual, on cats :)
')

All cycle about GObject:
GObject BasicsGObject: Inheritance and Interfaces
GObject: encapsulation, instantiation, introspectionInheritance from the descendants of GObject
In general, GObject implies only single inheritance. For multiple inheritance, it is proposed to use Java-like interfaces, which we will discuss in the second part of the article.
Create the Tiger type, which will inherit from Cat, which we described in the last article. This time we will make it a final-object and, thus, we will assume that there will be no further inheritance from it.
Create animaltiger.h:
#ifndef _ANIMAL_TIGER_H_ #define _ANIMAL_TIGER_H_ #include "animalcat.h" G_BEGIN_DECLS #define ANIMAL_TYPE_TIGER animal_tiger_get_type() G_DECLARE_FINAL_TYPE (AnimalTiger, animal_tiger, ANIMAL, TIGER, AnimalCat)
Notice that we used the G_DECLARE_FINAL_TYPE macro instead of the G_DECLARE_DERIVABLE_TYPE macro to describe the object of the final type, and also that the last argument of the macro was the “parent” AnimalCat.
If we assumed a further inheritance chain with a derivable-object, we would need to describe the class and create an instance of the parent class as the first field in it; in this case, we are not necessary.
Let's declare a function that returns a new instance of our tiger:
AnimalTiger* animal_tiger_new(); G_END_DECLS #endif /* _ANIMAL_TIGER_H_ */
Here it would be possible to declare other methods specific to the tiger object, but for simplicity, we limit ourselves to what it got from the parent - say_meow. This is a virtual function, which we will give a specific implementation of our type. After that close the macro G_END_DECLS. In principle, there is no need to frame the header file with the G_BEGIN_DECLS / G_END_DECLS macros, they are decomposed into the banal extern "C" {} and are needed for compatibility with C ++ compilers. These are just good form rules adopted in the GLib / GTK + development environment.
Let's start the file with the source code of animaltiger.c:
#include <stdio.h> #include "animaltiger.h" struct _AnimalTiger { AnimalCat parent; };
We connect the header and standard I / O and describe the structure-basis of our object. In this case, since we are dealing with a final-object, we did not create a class-structure, but the main structure, an instance-structure, must be described explicitly.
G_DEFINE_TYPE (AnimalTiger, animal_tiger, ANIMAL_TYPE_CAT)
In the initial macro, the last parameter is a macro that returns information about the type of the parent. If you remember, we have defined this macro in the header file animalcat.h.
Create a function animal_cat_real_say_meow ():
static void animal_tiger_real_say_meow(AnimalTiger* self) { printf("Tiger say: ARRRRGH!!!\n"); }
And two main functions:
static void animal_tiger_class_init(AnimalTigerClass* self) { printf("First instance of AnimalTiger was created.\n"); AnimalCatClass* parent_class = ANIMAL_CAT_CLASS (self); parent_class->say_meow = animal_tiger_real_say_meow; } static void animal_tiger_init(AnimalTiger* self) { printf("Tiger cub was born.\n"); }
In the animal_tiger_class_init function, which will be called when creating the first instance of our object, we get a pointer to the parent class using the ANIMAL_CAT_CLASS macro. This macro, like a number of others, is created when macros are expanded in header files, such as G_DECLARE_FINAL_TYPE. Next, we simply redefine the function to the one we created a few lines above. In our "constructor" animal_tiger_init () is nothing special, just let us know about the creation of our object.
In the animal_tiger_new () function, everything is arranged similarly to the parent class:
AnimalTiger* animal_tiger_new() { return g_object_new(ANIMAL_TYPE_TIGER, NULL); }
Let's check how our new type works:
#include "animaltiger.h" int main(int argc, char** argv) { AnimalTiger* tiger = animal_tiger_new(); animal_cat_say_meow(tiger); return 0; }
Let's add our Makefile from the previous article with new files, build and run:
First instance of AnimalCat was created. First instance of AnimalTiger was created. Little cat was born. Tiger cub was born. Tiger say: ARRRRGH!!!
As we see, at first, functions of the form _class_init are worked out, then the constructors of individual instances _init. When we call the method of the parent object and pass a pointer to the child instance as a parameter, the overridden function of the child is processed.
Interfaces
In addition to direct inheritance, GObject has the concept of interface inheritance. Interfaces are non-instantiable types, similar to the purely abstract C ++ classes or Java interfaces.
Let our tiger acquire the properties of a predator - it will implement the AnimalPredator interface (of course, all cats are predators, but we will not go into zoological details). Create the file animalpredator.h:
#ifndef _ANIMAL_PREDATOR_H_ #define _ANIMAL_PREDATOR_H_ #include <glib-object.h> G_BEGIN_DECLS #define ANIMAL_TYPE_PREDATOR animal_predator_get_type() G_DECLARE_INTERFACE (AnimalPredator, animal_predator, ANIMAL, PREDATOR, GObject) struct _AnimalPredatorInterface { GTypeInterface parent; void (*hunt)(AnimalPredator*); void (*eat_meat)(AnimalPredator*, int); }; void animal_predator_hunt(AnimalPredator* self); void animal_predator_eat_meat(AnimalPredator* self, int quantity); G_END_DECLS #endif /* _ANIMAL_PREDATOR_H_ */
As you can see, here everything looks like the heading of the usual GObject derivable-successor. Pay attention to a few points:
- the main macro here is G_DECLARE_INTERFACE instead of G_DECLARE_DERIVABLE_TYPE (and the last “argument” in this macro is still GObject);
- the class structure has a name like _AnimalPredatorInterface, not _AnimalPredatorClass;
- the first field in it is reserved for the GTypeInterface type, and not for the GObjectClass.
Also in the structure, we create two pointers for virtual functions that will be implemented by specific instantiated types. Finally, we declare two wrapper functions for these pointers — animal_predator_hunt and animal_predator_eat_meat.
Now we will create an auxiliary file, animalpredator.c, which should not be confused with the implementation of this interface - the resulting object module only starts specific implementations.
#include <stdio.h> #include "animalpredator.h" G_DEFINE_INTERFACE (AnimalPredator, animal_predator, G_TYPE_OBJECT) static void animal_predator_default_init(AnimalPredatorInterface* iface) { printf("The first instance of the object that implements AnimalPredator interface was created\n"); } void animal_predator_hunt(AnimalPredator* self) { AnimalPredatorInterface* iface = ANIMAL_PREDATOR_GET_IFACE (self); iface->hunt(self); } void animal_predator_eat_meat(AnimalPredator* self, int quantity) { AnimalPredatorInterface* iface = ANIMAL_PREDATOR_GET_IFACE (self); iface->eat_meat(self, quantity); }
Everything is simple: the initial macro G_DEFINE_INTERFACE, similar to G_DEFINE_TYPE, which we used at the beginning of animalcat.c and animaltiger.c, the constructor function animal_predator_default_init, into which you can put arbitrary code that runs when creating the first instance of an object that implements our interface and functions wrappers that launch specific implementations of the virtual interface functions. The ANIMAL_PREDATOR_GET_IFACE macro returns the class structure of the interface, just as the ANIMAL_CAT_CLASS macro returned the structure of the parent class.
We proceed to the implementation of our interface. Let's add our animaltiger.c:
#include "animalpredator.h" static void animal_tiger_predator_interface_init(AnimalPredatorInterface *iface);
The animal_tiger_predator_interface_init function must be declared at the beginning of the file, as it will be needed for the next macro.
Replace the macro G_DEFINE_TYPE with a similar one, but with more features. As you can see, a new “argument” has been added here, which may contain a number of macros that expand into arbitrary code. In this case, we put the macro G_IMPLEMENT_INTERFACE there.
G_DEFINE_TYPE_WITH_CODE (AnimalTiger, animal_tiger, ANIMAL_TYPE_CAT, G_IMPLEMENT_INTERFACE (ANIMAL_TYPE_PREDATOR, animal_tiger_predator_interface_init))
Add two functions that implement the "prototypes" of the class structure of the interface:
static void animal_tiger_predator_hunt(AnimalTiger* self) { printf("Tiger hunts. Beware!\n"); } static void animal_tiger_predator_eat_meat(AnimalTiger* self, int quantity) { printf("Tiger eats %d kg of meat.\n", quantity); }
Finally, we will create a constructor function implementing our interface, where we assign specific values ​​to the pointers from the interface structure defined above:
static void animal_tiger_predator_interface_init(AnimalPredatorInterface* iface) { iface->hunt = animal_tiger_predator_hunt; iface->eat_meat = animal_tiger_predator_eat_meat; }
Let's see how it works:
#include "animaltiger.h" #include "animalpredator.h" int main(int argc, char** argv) { AnimalTiger* tiger = animal_tiger_new(); animal_predator_hunt(tiger); animal_predator_eat_meat(tiger, 100500); }
We collect, we start:
First instance of AnimalCat was created. The first instance of the object that implements AnimalPredator interface was created First instance of AnimalTiger was created. Little cat was born. Tiger cub was born. Tiger hunts. Beware! Tiger eats 100500 kg of meat.
Is it possible to implement multiple interfaces at once? Yes of course. Here is the real code from GIO, where the object implements three interfaces at once:
static void initable_iface_init (GInitableIface *initable_iface); static void async_initable_iface_init (GAsyncInitableIface *async_initable_iface); static void dbus_object_manager_interface_init (GDBusObjectManagerIface *iface); G_DEFINE_TYPE_WITH_CODE (GDBusObjectManagerClient, g_dbus_object_manager_client, G_TYPE_OBJECT, G_ADD_PRIVATE (GDBusObjectManagerClient) G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init) G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init) G_IMPLEMENT_INTERFACE (G_TYPE_DBUS_OBJECT_MANAGER, dbus_object_manager_interface_init))
In this case, you need three separate _interface_init functions. Note that the last three macros are not separated by commas, this is one “argument”.
And what about the inheritance of interfaces? Here, the principle is also similar to that used in Java: this task is solved by the mechanism of prerequisites - type and object dependencies among themselves. For example, in order to implement the AnimalPredator interface, you will also need to implement the Eater interface or be inherited from an object, say, Mammal. However, consideration of these nuances threatens to dramatically increase the volume of the article, so we leave it outside the scope of this text.