📜 ⬆️ ⬇️

GObject: encapsulation, instantiation, introspection

... as well as other scary words! (with)

Before we get acquainted with some of the advanced capabilities of the GLib object type system, we need to talk about a number of points that we did not touch on in the previous two articles. This time we will get acquainted closer with the basic type of GObject, let's talk about the fact that any heir of the basic GObject is a dual unity (and often a triunity) of individual structure objects, into which the mysterious macros at the beginning of header files and source code files are revealed with what toolkit does harsh local RTTI work, why does GObject and its descendants have two destructors (and three constructors), as well as a number of other interesting trifles.

image

All cycle about GObject:


GObject Basics
GObject: Inheritance and Interfaces
GObject: encapsulation, instantiation, introspection
')

Structures. Many structures.


As we know, the descendants of GObject can be inheritable - derivable and non-inherited - final. In the general case, a derivable GObject consists of a set of three objects: a class structure, an instance structure, and a private data structure.

With a class structure, everything is more or less simple - it is described in the header file and contains an instance of the parent class structure and function pointers - “virtual methods”. It is considered good practice to add a small array of void-pointers to the last field of the structure to ensure ABI-compatibility. An instance of such a structure is created in one instance when creating the first instance of this type.

/* animalcat.h */ /*   ,      :) */ typedef struct _AnimalCat AnimalCat; typedef struct _AnimalCatClass AnimalCatClass; typedef struct _AnimalCatPrivate AnimalCatPrivate; struct _AnimalCatClass { GObjectClass parent_class; /*    */ void (*say_meow) (AnimalCat*); /*   */ gpointer padding[10]; /*  ; gpointer -  void* */ }; 

For final types, there is no need to define a class structure.

A private data structure is needed for derivable objects. It is defined in the source file, and access to it can be obtained through an automatically generated function of the form animal_cat_get_instance_private (). In this case, the macro at the beginning of the .c file must be of the form G_DEFINE_TYPE_WITH_PRIVATE (NamespaceObject, namespace_object, PARENT_TYPE). You can also use the G_DEFINE_TYPE_WITH_CODE macro (with the G_ADD_PRIVATE macro included in it).

 /* animalcat.c */ #include "animalcat.h" G_DEFINE_TYPE_WITH_PRIVATE(AnimalCat, animal_cat, G_TYPE_OBJECT) /* G_DEFINE_TYPE_WITH_CODE(AnimalCat, animal_cat, G_TYPE_OBJECT, G_ADD_PRIVATE (AnimalCat)) */ struct _AnimalCatPrivate { char* name; double weight; int age; }; static void animal_cat_init(AnimalCat* self) { AnimalCatPrivate* priv = animal_cat_get_instance_private(self); priv->age = 0; priv->name = "Barsik"; /*    */ } 

It is assumed that all data is encapsulated. To access them, you can use the usual wrappers - getters and setters, but, as we will see later, GObject provides for this where as a more powerful tool - properties.

A structure instance, as well as a structure with private data, is created for each object instance. This is, in fact, the object itself, with which the end user will mainly work. The structure is automatically generated for derivable types via a macro from the header file, so the programmer does not need to do it himself. For final types, it must be described manually in the source file. Since in this case the structure is not part of the public interface of the object, it may contain private data. Obviously, in this case there is no need to create a separate private structure.

 /* animaltiger.c */ struct _AnimalTiger { AnimalCat parent; /*         */ int speed; /*   */ }; 

As for interfaces, for their implementation it is necessary to define only an interface structure that is very similar to a regular class structure. The structure of the object itself _AnimalPredator will be generated automatically.

 /* animalpredator.h */ typedef struct _AnimalPredatorInterface AnimalPredatorInterface; struct _AnimalPredatorInterface { GTypeInterface parent; /*     GTypeInterface */ void (*hunt) (AnimalPredator* self); /*   */ }; 


Visual table-crib:
image

Dynamic type determination in practice


In the header files, we started the description of a new type with the use of two macros, which, in turn, are converted into a whole set of macros. In older versions of GLib, it was necessary to manually describe all of this toolkit. Let's see what we can use from this.

ANIMAL_TYPE_CAT: returns an integer identifier of type GType. This macro is closely related to the GType type system that underlies the GObject. You will surely meet him, I mentioned him only to make it clear where he comes from. Functions like animal_cat_get_type () that this macro uses are generated automatically in the source file when macros of the G_DEFINE_TYPE family are opened.

ANIMAL_CAT (obj): cast to a pointer to this type. Provides a safe caste, and also performs the execution time checks. As you can see, the inheritance system in GObject is generally based on the fact that structures contain an instance of the parent structure in the first field, which means that according to the C call conventions, the object pointer matches the pointer to all ancestors from which it is inherited. Despite this, it is advisable to use the provided macro, and not the usual C-caste. In addition, in some cases (for example, when casting to the type of interface implemented), a C-style cast will not work at all.

ANIMAL_CAT_CLASS (klass): similar macro for class structures. The agreement prescribes not to use the word class for compatibility with C ++ compilers.

ANIMAL_IS_CAT (obj): as the name suggests, this macro determines whether obj is a pointer to a given type (and is not a NULL pointer). It is considered good practice to begin object methods with such a check.

 void animal_cat_run (AnimalCat *self) { assert(ANIMAL_IS_CAT (self)); g_return_if_fail (ANIMAL_IS_CAT (self)); /*   GLib */ /*   */ } 

ANIMAL_IS_CAT_CLASS (klass): the same for class structures.

ANIMAL_CAT_GET_CLASS (obj): returns a pointer to the corresponding class structure.

A similar set of macros is generated for interfaces.

ANIMAL_PREDATOR (obj): castes to an interface type.
ANIMAL_IS_PREDATOR (obj): type matching check.
ANIMAL_PREDATOR_GET_IFACE (obj): getting interface structure.

The object name can be obtained using the macro G_OBJECT_TYPE_NAME (obj), which returns a C string with the type name.

The macros at the beginning of the source code file G_DEFINE_TYPE and its extended versions generate a pointer of the form animal_cat_parent_class, which returns a pointer to the class structure of the parent object, as well as a function of the form animal_cat_get_instance_private () if we used the corresponding macro.

Destructors and other virtual functions


As we remember, when creating any successor to GObject, functions of the form animal_cat_init () are run. They perform the same role as C ++ and Java constructors. With destructors, the situation is more complicated.

Memory management in GObject is implemented using reference counting. When calling the g_object_new () function, the number of links is set to one. In the future, we can increase their number with g_object_ref () and reduce it with g_object_unref (). When the number of links becomes zero, the procedure for the destruction of an object consisting of two phases will be launched. First, the dispose () function is called, which can be called multiple times. Its main task is to resolve circular references if necessary. After this, the function finalize () is called once, in which everything that destructors are usually used for is executed - memory is freed, open file scripts are closed, etc.

Such a complex system was designed to facilitate the creation of binding to high-level languages, including automatic memory management. In practice, in C code, usually only finalize () is used, if the object assumes the presence of a destructor.

The functions dispose () and finalize (), as well as a number of others, which we will discuss later, are virtual and defined in the GObjectClass.

 static void animal_cat_finalize(GObject* obj) { g_print("Buy!\n"); /*  printf()  GLib */ /*    . . */ G_OBJECT_CLASS (animal_cat_parent_class)->finalize(obj); /*         */ } static void animal_cat_class_init(AnimalCatClass* klass) { GObjectClass* obj_class = G_OBJECT_CLASS (klass); obj_class->finalize = animal_cat_finalize; /*   */ } 

The last line of the animal_cat_finalize () function may seem to require further explanation. The pointer animal_cat_parent_class to the parent class is created when the macro G_DEFINE_TYPE and its extended versions are expanded. We call the corresponding function from the parent class, which in this case is directly a GObjectClass structure, which, in turn, calls the finalize () of the previous class in the chain. There is no need to worry that the parent class may not contain the finalize () override, the GObject system will take care of this.

It remains only to recall that the destructor is called only when the reference count is reset:

 int main(int argc, char** argv) { AnimalCat* cat = animal_cat_new(); g_object_unref(cat); /*      */ } 

In addition to two destructors, GObjectClass contains two additional virtual constructors. constructor () is called before animal_cat_init () that is already known to us and directly creates an instance of this type, constructed () after it. It is not easy to think of a situation in which you need to redefine these functions, unless of course you decide to patch the GLib itself. In the documentation, developers give an example with the implementation of a singleton, but in real code I have never seen such cases. However, to achieve maximum flexibility at all stages of the object's instance life cycle, the developers considered it necessary to make these functions virtual.

In addition, GObjectClass contains the virtual functions get_property () and set_property (), which must be redefined to use such powerful features of the basic type GObject and its descendants as properties in their own objects. We will talk about this in the next article.

Source: https://habr.com/ru/post/418443/


All Articles