Friday, September 7, 2018

Part 2. Deriving from GObject based object

In the previous post, we discussed how to derive a final class from the GObject class. Here, I would like to focus on a derived object that can be used for the following subclassing. This is a common situation for library development, where classes, usually, are expected to be further subclassed. We will continue our journey using the same files from the previous post: my-point.h and my-point.c. As usual, let's start with the class declaration in the header file. We need to change our declaration macro and add some additional code. For declaration, we will use the following macro:

G_DECLARE_DERIVABLE_TYPE()


As it says, it is used specifically to declare a class that can be further subclassed. It should be noted here, that it is better to use final class by default and use derivable only if it is used for further subclassing. It is always possible to convert a final class to a non-final without breaking ABI but not wise verse. For a derivable class, we need to introduce a class instance structure. The purpose of this structure is to define methods that can be overridden in the derived class. We can view these methods as virtual in the C++ language. The complete header file would be:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef _my_point_h_
#define _my_point_h_

#include <glib-object.h>

#define MY_TYPE_POINT  my_point_get_type ()

G_DECLARE_DERIVABLE_TYPE (MyPoint, my_point, MY, POINT, GObject)

struct _MyPointClass
{
  GObjectClass parent_class;

  void (*move) (MyPoint *obj,
                gint x,
                gint y);
  gpointer padding[12];
};

MyPoint *my_point_new (void);

#endif

Let's take a moment and review what we have here. The _MyPointClass structure should be defined after the G_DECLARE_... macro. It contains a pointer to the function (move). It also contains 12 additional pointers that can be further used for other methods without breaking the ABI. That's it. 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 use the simplest form the G_DEFINE_.. family:

G_DEFINE_TYPE_WITH_PRIVATE()


For the derivable class, we need also a special private structure instance declared as <object_name>Private and defined in the source file:

1
2
3
4
5
6
typedef struct {
  gint x;
  gint y;
  gchar *name;
  /* stuff */
} MyPointPrivate;

This structure should be defined before the G_DEFINE_TYPE_WITH_PRIVATE() macro. Similar to the final class we also need to define a series of static methods:

my_point_class_init
my_point_init
my_point_dispose
my_point_finalize

We don't need to redefine dispose if we have no references to drop. Similar, we don't need to redefine finalize if no memory should be freed for the class members. Here we do this only to show how it should be done if needed. Our source file, for now, should look like following:

 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
50
51
52
#include "my-point.h"

typedef struct
{
  gint x;
  gint y;
  gchar *name;
} MyPointPrivate;

G_DEFINE_TYPE_WITH_PRIVATE (MyPoint,my_point,G_TYPE_OBJECT)

MyPoint*
my_point_new (void)
{
  return g_object_new (MY_TYPE_POINT, NULL);
}

static void
my_point_init (MyPoint *self)
{
  MyPointPrivate *priv = my_point_get_instance_private (self);
  priv->x = 1; /* private member initialization */
  priv->name = g_strdup("Hello, world!");
/* All object should be initialized here */
}

static void
my_point_dispose (GObject *object)
{
  MyPoint *self = MY_POINT(object);
  MyPointPrivate *priv = my_point_get_instance_private (self);
/* Drop the references here */
  G_OBJECT_CLASS(my_point_parent_class)->dispose(object);
}

static void
my_point_finalize (GObject *object)
{
  MyPoint *self = MY_POINT(object);
  MyPointPrivate *priv = my_point_get_instance_private (self);
 
  g_free (priv->name);

  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;
}


There is a moment here that differentiates the derivable class implementation from the final one. It is an access to the class members. Now, we can't get access to our class members by dereferencing the object pointer. Instead, we need to create a special instance of our private structure. The following line of code does exactly what we need:

MyPointPrivate *priv = my_point_get_instance_private (self);

After that, any class member can be accessed by dereferencing the private structure instance priv. The function my_point_get_instance_private() we call here is defined by the G_DEFINE_TYPE_WITH_PRIVATE() macro. We don't need to free the memory for priv pointer. Implementation of all methods should be understandable from the part 1. To destroy our class and free memory, we can also call g_object_unref or g_clear_object methods or wrap them into the my_point_free method as we did for the final class.

Now it is time to go back to our class instance structure defined in the header file. We have a pointer move to a function. If we set this pointer to NULL in ..._class_init() method, it will be equivalent to the pure virtual method in the C++ language. The pointer should be set in the derived class. Otherwise, we need to define a default implementation as a static method, e.g.:

1
2
3
4
5
6
7
8
static void
_my_point_real_move (MyPoint *self, gint x, gint y)
{
  MyPointPrivate *priv = my_point_get_instance_private (self);

  priv->x += x;
  priv->y += y; 
}

After that, we can initiate our move pointer to the default implementation by putting

object_class->move = _my_point_real_move;


to the ..._class_init() method. Ok, we initialized our move pointer but how can we call a method to execute the function our move is pointing? We need a public API declared in the header and implemented in the source file. We should add the following code to the header file:

1
 void my_point_move (MyPoint *self, gint x, gint y);

And the definition of this method should look like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void  
my_point_move (MyPoint *self, gint x, gint y)
{
  g_return_if_fail (MY_IS_POINT(self));

  MyPointClass *klass = MY_POINT_GET_CLASS (self);
 
  g_return_if_fail (klass->move != NULL);

  klass->move(self,x,y); 
}

Let's review what we have her. At line #4 we need to check that self is really a pointer to MyPoint. At line #6 we create an instance for our class, check that our pointer to function is not NULL, at line #8, and call the actual method on line #10. That's it. Now if we call my_point_move(), the method pointed by the move pointer will be executed. In our case it is _my_point_real_move(). To reimplement my_point_move() method in the derived class, we just need to point our move pointer to a different implementation. To do so, we need to create an instance of MyPointClass from the derived class instance in the derived ..._class_init() method and redefine the pointer:

1
2
3
4
5
6
7
static void
my_point_derived_class_init (MyPointDerivedClass *klass)
{
  MyPointClass *pklass = MY_POINT_CLASS (klass);

  pklass->move = _my_point_new_move;
}

The implementation method should have the same signature as the pointer. In our case  - move. If we remove the public API to call our method pointed by the move pointer we will get a private virtual method which can be accessed from the source code only. To make it even more interesting, lets' add two public API to get the class member name and to set it. We need to declare the public API in the header file and implement in the source file. The implementation should be self-explained. We return const gchar* from the .._get_name() because we don't want to modify out class member by the returned pointer. In the next posts, we will see a general approach to handle getters and setters for a class. Now, let's summarize what we have. The header file should be:

 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
#ifndef _my_point_h_
#define _my_point_h_

#include <glib-object.h>

#define MY_TYPE_POINT  my_point_get_type ()

G_DECLARE_DERIVABLE_TYPE (MyPoint, my_point, MY, POINT, GObject)

struct _MyPointClass
{
  GObjectClass parent_class;

  void (*move) (MyPoint *obj,
                gint x,
                gint y);
  gpointer padding[12];
};

MyPoint     *my_point_new  (void);
void         my_point_free (MyPoint *self); 

void         my_point_move (MyPoint *self, gint x, gint y);

void         my_point_set_name (MyPoint *self, const gchar *newname);
const gchar *my_point_get_name (MyPoint *self);

#endif

And the source file should be:

 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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#include "my-point.h"

typedef struct
{
  gint x;
  gint y;
  gchar *name;
} MyPointPrivate;

G_DEFINE_TYPE_WITH_PRIVATE (MyPoint,my_point,G_TYPE_OBJECT)

MyPoint*
my_point_new (void)
{
  return g_object_new (MY_TYPE_POINT, NULL);
}

void
my_point_free (MyPoint *self)
{
  g_clear_object (&self);
}

static void
_my_point_real_move (MyPoint *self, gint x, gint y)
{
  MyPointPrivate *priv = my_point_get_instance_private (self);

  priv->x += x;
  priv->y += y; 
}

static void
my_point_init (MyPoint *self)
{
  MyPointPrivate *priv = my_point_get_instance_private (self);
  priv->x = 1; /* private member initialization */
  priv->name = g_strdup("Hello, world!");
/* All object should be initialized here */
}

static void
my_point_dispose (GObject *object)
{
  MyPoint *self = MY_POINT(object);
  MyPointPrivate *priv = my_point_get_instance_private (self);
/* Drop the references here */
  G_OBJECT_CLASS(my_point_parent_class)->dispose(object);
}

static void
my_point_finalize (GObject *object)
{
  MyPoint *self = MY_POINT(object);
  MyPointPrivate *priv = my_point_get_instance_private (self);
 
  g_free (priv->name);

  G_OBJECT_CLASS(my_point_parent_class)->finalize(object);
}

void  
my_point_move (MyPoint *self, gint x, gint y)
{
  g_return_if_fail (MY_IS_POINT(self));

  MyPointClass *klass = MY_POINT_GET_CLASS (self);
 
  g_return_if_fail (klass->move != NULL);

  klass->move(self,x,y); 
}

void
my_point_set_name (MyPoint *self, const gchar *newname)
{
  MyPointPrivate *priv = my_point_get_instance_private (self);
  g_free (priv->name);
  priv->name = g_strdup(newname);
}

const gchar*
my_point_get_name (MyPoint *self)
{
  MyPointPrivate *priv = my_point_get_instance_private (self);
  return priv->name;
}

static void
my_point_class_init (MyPointClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  klass->move = _my_point_real_move;
  klass->dispose = my_point_dispose;
  klass->finalize = my_point_finalize;
}

No comments:

Post a Comment