I remember, when I started my journey in the forest called Glib/GObject [gobject] it was a disaster. I didn't understand basic concepts and had a very hard time to switch my brain from C++ syntax and semantics to Glib style and concepts. Of course, I read tutorial [tutorial] multiple times and was able quickly to recognize where to look what by memorization, but still didn't have an understanding of how to implement an object (final and derivable) or an interface. I was totally confused by a variety of different macro available to declare/define a new object.
In this post, I am not going to retell the Glib manual but will provide my, hopefully, simple and clear, overview of how to start with Glib fearlessly. I found the official Glib documentation very technical and hard to read for new people (I am speaking for myself). Indeed, when you know what you are doing you don't need a tutorial. You need a reference. For beginners, on the other hand, the situation is different. They may come with a different background and experience and tutorial should teach them how to do things in a correct way. The situation becomes even more confused for beginners if they check the existing code in some projects, e.g. GTK+[gtk]. The GObject tutorial [style] encourages to use so-called "modern" way to implement objects but a lot of existing code still contains old style code. I will focus on the new style code here and will not mention the old one.
So let's begin. Here, we will review how to declare and define GObject based object which can be a final object. Let's start with the general file layout. To implement a new object we need two files: header (*.h) and source (*.c). The header file contains a declaration of an object, while the source contains implementation. We should also mention a convention used for file and method naming. All methods for our class will contain a prefix in the format: <namespace>_<module_name>_. Thus, for the GTK+ namespace would be "gtk", for the GLib project, namespace would be "g". The "module_name" corresponds to the name of our object. Thus, for class GtkButton the prefix would look like: gtk_button_. The full method name would look like: gtk_button_new(). You got it, right? See official convention for details [convention].
We will start with the header file "my-point.h". There is a macro available to declare the final class.
G_DECLARE_FINAL_TYPE()
It is used to declare a final object, that can't be used for the following inheritance. It is similar to the following C++ semantic.
class Someclass final
{
};
These type of classes are very common for GUI development using GTK+ widgets. To define the final object "MyPoint" the following header file can be used. The object type, MY_TYPE_POINT in our case, should be defined for every listed above declaration.
1 2 3 4 5 6 7 8 9 10 11 12 | #ifndef _MY_POINT_H_ #define _MY_POINT_H_ #include <glib-gobject.h> #define MY_TYPE_POINT my_point_get_type () G_DECLARE_FINAL_TYPE (MyPoint, my_point, MY, POINT, GObject) MyPoint *my_pointer_new (void); #endif |
Let's quickly review the arguments we pass to the G_DECLARE_FINAL_TYPE.
arg1: "MyPoint" - a name of the object
arg2: "my_point" - a prefix for our methods
arg3: "MY" - a namespace for our object
arg4: "POINT" - a module name
arg5: "GObject" - a parent class type
That is. Now, we can go to our source file and complete the implementation/definition of the object. There are multiple macro available to do so. See the Glib tutorial for the complete list of G_DEFINE_… macro. Here we will discuss the most important for us:
G_DEFINE_TYPE()
It is used for a final object that is not planned to be further subclassed.
Now, it is time to think about where we going to keep our private class members. All class members should be stored in the object instance structure. This structure should be defined in the source file and because of that it is not visible for other files, e.g. becomes private:
1 2 3 4 5 6 7 | struct _MyPoint { GObject parent_instance; gint x; gint y; gchar *name; }; |
The first member should be a parent instance. In our case it is GObject. Pay attention it is not a pointer. The instance structure should be defined before calling G_DEFINE_TYPE_… macro. Putting all together we have:
1 2 3 4 5 6 7 8 9 10 11 | #include "my-point.h" struct _MyPoint { GObject parent_instance; gint x; gint y; gchar *name; }; G_DEFINE_TYPE (MyPoint,my_point,G_TYPE_OBJECT) |
To correctly initialize our object and class, we need to define two static methods. The should be declared in the source file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | static void my_point_class_init (MyPointClass *klass) { } static void my_point_init (MyPoint *self) { self->x = 1; self->name = g_strdup("Hello, world!"); /* All other class members should be initialized here */ } |
my_point_class_init performs class-wide initialization and executed only once even if you have 100 objects of your class. my_point_init performs initialization of the object instance and executed every time you create a new object of your class. As you can see, we initialized some class member in the instance init method. Now, what about my_point_new() function we declared in the header file? Of course, it should be defined in our source file as a normal (non-static) function:
1 2 3 4 5 | MyPoint* my_point_new (void) { return g_object_new (MY_TYPE_POINT, NULL); } |
As we can see, the role of my_point_new() method is just to create an instance of our object. The method g_object_new() will invoke all other methods to correctly initialize our class and object instance. It is a convenient wrapper around g_object_new() method.
Ok, everything looks clear, we can declare an object, define, initialize but what about clean up? A C++ programmer would expect something similar to the destructor. There are two functions exist in the GObject world to serve the same purpose:
static void my_point_dispose(GObject *object)
and
static void my_point_finalize(GObject *object)
The ..._dispose() method is an entry point for the cleanup process. We need to drop all references to memory that had been passed in through the MyPoint API. In the ..._finilize() function, we should free memory for all private objects we allocated earlier. Both functions should be defined as static and implemented in the source file. We need to call the same dispose and finalize functions for our parent class and every parent class for the parent class should also call these functions. To do so, we should finish the …_dispose() and ..._finalize() methods by calling the same .._dispose() and ..._finalize() methods for the parent object using
G_OBJECT_CLASS (<object_prefix>_parent_class)->dispose (gobject);
and
G_OBJECT_CLASS (<object_prefix>_parent_class)->finalize (gobject);
The .._dispose() method may be called multiple times. The final definition of the ..._dispose() method would look like:
1 2 3 4 5 6 7 | static void my_point_dispose (GObject *object) { MyPoint *self = MY_POINT(object); /* Drop all references to memory passed in through the object API */ G_OBJECT_CLASS(my_point_parent_class)->dispose(object); } |
The argument (my_point_parent_class) we pass to G_OBJECT_CLASS macro is defined automatically by G_DEFINE_… macro earlier. We need also point our class to a method it should use for disposing and finalizing processes. It should be done in the class_init method by setting dispose and finalize pointers to point to our methods:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | static void my_point_dispose (GObject *object) { MyPoint *self = MY_POINT(object); /* Drop all references to memory passed in through the object API */ G_OBJECT_CLASS(my_point_parent_class)->dispose(object); } static void my_point_finalize (GObject *object) { MyPoint *self = MY_POINT(object); g_free (self->name); /* Finalize code cleanup */ G_OBJECT_CLASS(my_point_parent_class)->finalize(object); } static void my_point_class_init (MyPointClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->dispose = my_point_dispose; object_class->finalize = my_point_finalize; } |
Of course, if you have no references to clean in ..._dispose() method, it is not necessary to redefine this method. Now, since we have ..._dispose() and ..._finalize() method, how to initiate the object destroy process and free the memory? I personally like to have <prefix>_free() method that can implement correct cleaning and keep API consistent. It should be public as same as <prefix>_new() and accept our object type as the argument. Thus, we should add the following declaration to the header file:
void my_point_free (MyPoint *obj);
and implement it as follow in the source file:
void my_point_free (MyPoint *obj)
{
g_clear_object (&obj);
}
Method g_clear_object() is a convenient method to free memory. It clears memory and set object pointer to NULL. Otherwise, standard g_object_unref() can be used for all GObject based classes.
That's it. We can now create a final class derived from the GObject based class using my_point_new() method and destroy it using my_point_free() method. The final header file should be:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #ifndef _MY_POINT_H_ #define _MY_POINT_H_ #include <glib-object.h> G_BEGIN_DECLS #define MY_TYPE_POINT my_point_get_type () G_DECLARE_FINAL_TYPE (MyPoint, my_point, MY, POINT, GObject) MyPoint *my_pointer_new (void); void my_pointer_free (MyPoint* self); G_END_DECLS #endif |
And source file would look like:
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 | #include "my-point.h" struct _MyPoint { GObject parent_instance; gint x; gint y; gchar *name; }; G_DEFINE_TYPE (MyPoint,my_point,G_TYPE_OBJECT) static void my_point_init (MyPoint *self) { self->x = 1; /* private member initialization */ self->name = g_strdup("Hello, world!"); /* All object should be initialized here */ } static void my_point_dispose (GObject *object) { MyPoint *self = MY_POINT(object); G_OBJECT_CLASS(my_point_parent_class)->dispose(object); } static void my_point_finalize (GObject *object) { MyPoint *self = MY_POINT(object); g_free (self->name); /* Finalize code cleanup */ G_OBJECT_CLASS(my_point_parent_class)->finalize(object); } static void my_point_class_init (MyPointClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->dispose = my_point_dispose; object_class->finalize = my_point_finalize; } void my_point_free (MyPoint *obj) { g_clear_object (&obj); } |
G_BEGIN_DECLS and G_END_DECLS are the convenient macro that help with C++ integration. To be continued.....
I received very valuable comments from Philip Chimento. The post was edited to incorporated those suggestions. Thank you, Philip.
ReplyDelete