Friday, September 7, 2018

Part 3. Deriving from GObject derived class - Object properties

In the previous post, we introduced a concept about getter and setter methods. Basically, they provide safer access to the class members. However, it is absolutely not necessary to have getters and/or setters for all class member. The decision to have or not to have getters and setters should be driven by the class design logic. Here, we will review two approaches to implement get/set methods. Let's start with an example where public API available to serve our getter and setter purpose. We continue our work on the MyPoint class expanding it. Let's add a public API to access our name member. As a reminder, our public structure should be:

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

Which corresponds to the derivable class from the part 2. For public API, we need to declare our methods in the header file and define them in the source file. The declaration in the header file is pretty straightforward and will be:

1
2
void my_point_set_name (MyPoint *self, const gchar *name);
const gchar *my_point_get_name (MyPoint *self);

The definition in 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
void
my_point_set_name (MyPoint *self, 
                   const gchar *name)
{
  g_return_if_fail (self);
  
  MyPointPrivate *priv = my_point_get_instance_private (self);
  
  g_free (priv->name);
  priv->name = g_stdup (name);
}

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

Did you notice some extra calls on line #5 and #16? They are common checkers for method parameters. Without those checkers, we may pass a NULL as the self argument and it will cause an error. Since we are trying to dereference it in the function. With these checkers, we know for sure that self is not NULL. GObject provides an alternative and a general approach to accomplish the same task. It is called GObject properties. How it works? We introduce a property for an object and associate that property to a class member. After that, when we manipulate the property we manipulate our class member. Let's implement a property for the class member name for our MyPoint class.

 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
enum {
  PROP_0,
  PROP_NAME,
  N_PROPERTIES
};

static GParamSpec *properties[N_PROPERTIES] = {NULL};

static void
my_point_set_property (GObject      *object,
                       guint         property_id,
                       const GValue *value,
                       GParamSpec   *pspec)
{
  MyPoint *self = MY_POINT(object);
  MyPointPrivate *priv = my_point_get_instance_private (self);

  switch (property_id)
    {
      case PROP_NAME:
        g_free (priv->name);
        priv->name = g_value_dup_string (value);
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}

static void
my_point_get_property (GObject      *object,
                       guint         property_id,
                       GValue       *value,
                       GParamSpec   *pspec)
{
  MyPoint *self = MY_POINT(object);
  MyPointPrivate *priv = my_point_get_instance_private (self);

  switch (property_id)
    {
      case PROP_NAME:
        g_value_set_string (value, priv->name);
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}


static void
my_point_class_init (MyPointClass *klass)
{
  GObjecClass *cobject = G_OBJECT(klass);

  cobject->get_property = my_point_get_property;
  cobject->set_property = my_point_set_property; 

  properties[PROP_NAME] = g_param_spec_string ("name",
                         "Name",
                         "Name of our point",
                         "Default value",
                         G_PARAM_READWRITE));  
  
  g_object_class_install_properties (cobject,N_PROPERTIES,properties);
}

Let's review this code line-by-line. In the beginning, we declare an enumerator that defines all properties. The property with value "0" is reserved for internal GObject use and should not be used in the user-written code. We can even remove it from our declaration and set the next member to have value "1":

1
2
3
4
enum {
  PROP_NAME = 1,
  N_PROPERTIES
};

On the line #7 (previous block) we declare an array to hold our properties and set all values to NULL. In the next two blocks, we define static functions to implement a code for our object to work with our properties. At the beginning of the function's body, we get an object to the private structure and then use the switch statement to work with each property based on passed property ID, which in fact enumerator values defined earlier. Pay attention to the object type we pass to our static methods. It is GValue. Because of that, it is very general and a user only needs to implement how to set/get values to the GValue variable. Exactly what we have in our switch statement. GObject class defines two pointers to functions that we need to set using our static functions: set_property and get_property (lines #56 and #57).  The next step would be setting values to our properties array. We should do this in our ..._class_init function. One-by-one, we need to set values for every element of our array. g_param_spec_... family of functions provides a convenient mechanism to create a GParamSpec instance. The user should use an appropriate function that corresponds to the type of member. In our case it is const gchar* , therefore, we use g_param_spec_string method. Please see a full list of the available methods in the official documentation. In the last step (line #65), we register our array with properties. That's it. Now, if we want to get a value of our name class member we need to use g_object_get() method and to set a new value for name, we should use g_object_set() method:

1
2
3
4
5
gchar *buffer;
MyPoint *point;

g_object_get (point,"name",&buffer,NULL);
g_object_set (point,"name",buffer,NULL);

The code should be self-explained. The first argument for both methods is our MyPoint instance. The second argument is our property name. To get a value we need to pass a reference to an object where we would like to have a copy. To set a new value we need to pass a regular object instance. We can repeat this step and get as many as we need properties at once. Just add more property-value pairs. In the end, we should terminate with NULL.
As you can see, two approaches are very similar for API user and can be used interchangeably as soon as an appropriate code to define properties was implemented. For library development, the generic approach is favorable since it provides a better binding implementation for other languages.

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