The GLib Dynamic Type System

A type, as manipulated by the GLib type system, is much more generic than what is usually understood as an Object type. It is best explained by looking at the structure and the functions used to register new types in the type system.

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
typedef struct _GTypeInfo               GTypeInfo;
struct _GTypeInfo
{
  /* interface types, classed types, instantiated types */
  guint16                class_size;
  
  GBaseInitFunc          base_init;
  GBaseFinalizeFunc      base_finalize;
  
  /* classed types, instantiated types */
  GClassInitFunc         class_init;
  GClassFinalizeFunc     class_finalize;
  gconstpointer          class_data;
  
  /* instantiated types */
  guint16                instance_size;
  guint16                n_preallocs;
  GInstanceInitFunc      instance_init;
  
  /* value handling */
  const GTypeValueTable *value_table;
};
GType g_type_register_static (GType             parent_type,
                              const gchar      *type_name,
                              const GTypeInfo  *info,
                              GTypeFlags        flags);
GType g_type_register_fundamental (GType                       type_id,
                                   const gchar                *type_name,
                                   const GTypeInfo            *info,
                                   const GTypeFundamentalInfo *finfo,
                                   GTypeFlags                  flags);

g_type_register_static, g_type_register_dynamic and g_type_register_fundamental are the C functions, defined in gtype.h and implemented in gtype.c which you should use to register a new GType in the program's type system. It is not likely you will ever need to use g_type_register_fundamental but in case you want to, the last chapter explains how to create new fundamental types.

Fundamental types are top-level types which do not derive from any other type while other non-fundamental types derive from other types. Upon initialization, the type system not only initializes its internal data structures but it also registers a number of core types: some of these are fundamental types. Others are types derived from these fundamental types.

Fundamental and non-fundamental types are defined by:

  • class size: the class_size field in GTypeInfo.

  • class initialization functions (C++ constructor): the base_init and class_init fields in GTypeInfo.

  • class destruction functions (C++ destructor): the base_finalize and class_finalize fields in GTypeInfo.

  • instance size (C++ parameter to new): the instance_size field in GTypeInfo.

  • instantiation policy (C++ type of new operator): the n_preallocs field in GTypeInfo.

  • copy functions (C++ copy operators): the value_table field in GTypeInfo.

  • type characteristic flags: GTypeFlags.

Fundamental types are also defined by a set of GTypeFundamentalFlags which are stored in a GTypeFundamentalInfo. Non-fundamental types are furthermore defined by the type of their parent which is passed as the parent_type parameter to g_type_register_static and g_type_register_dynamic.

Copy functions

The major common point between all GLib types (fundamental and non-fundamental, classed and non-classed, instantiable and non-instantiable) is that they can all be manipulated through a single API to copy/assign them.

The GValue structure is used as an abstract container for all of these types. Its simplistic API (defined in gobject/gvalue.h) can be used to invoke the value_table functions registered during type registration: for example g_value_copy copies the content of a GValue to another GValue. This is similar to a C++ assignment which invokes the C++ copy operator to modify the default bit-by-bit copy semantics of C++/C structures/classes.

The following code shows how you can copy around a 64 bit integer, as well as a GObject instance pointer:

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
static void test_int (void)
{
  GValue a_value = G_VALUE_INIT;
  GValue b_value = G_VALUE_INIT;
  guint64 a, b;

  a = 0xdeadbeef;

  g_value_init (&a_value, G_TYPE_UINT64);
  g_value_set_uint64 (&a_value, a);

  g_value_init (&b_value, G_TYPE_UINT64);
  g_value_copy (&a_value, &b_value);

  b = g_value_get_uint64 (&b_value);

  if (a == b) {
    g_print ("Yay !! 10 lines of code to copy around a uint64.\n");
  } else {
    g_print ("Are you sure this is not a Z80 ?\n");
  }
}

static void test_object (void)
{
  GObject *obj;
  GValue obj_vala = G_VALUE_INIT;
  GValue obj_valb = G_VALUE_INIT;
  obj = g_object_new (VIEWER_TYPE_FILE, NULL);

  g_value_init (&obj_vala, VIEWER_TYPE_FILE);
  g_value_set_object (&obj_vala, obj);

  g_value_init (&obj_valb, G_TYPE_OBJECT);

  /* g_value_copy's semantics for G_TYPE_OBJECT types is to copy the reference.
   * This function thus calls g_object_ref.
   * It is interesting to note that the assignment works here because
   * VIEWER_TYPE_FILE is a G_TYPE_OBJECT.
   */
  g_value_copy (&obj_vala, &obj_valb);

  g_object_unref (G_OBJECT (obj));
  g_object_unref (G_OBJECT (obj));
}

The important point about the above code is that the exact semantics of the copy calls is undefined since they depend on the implementation of the copy function. Certain copy functions might decide to allocate a new chunk of memory and then to copy the data from the source to the destination. Others might want to simply increment the reference count of the instance and copy the reference to the new GValue.

The value table used to specify these assignment functions is documented in GTypeValueTable.

Interestingly, it is also very unlikely you will ever need to specify a value_table during type registration because these value_tables are inherited from the parent types for non-fundamental types.