Purpose of GValue, GTypeValueTable, GTypeInfo and GParamSpec
Asked Answered
F

1

8

GObject library is really awfully documented. It's damn hard to figure the purposes of entities created. Namely, I don't get the roles of GValue, GTypeValueTable, GTypeInfo, GParamSpec and TypeData.

In brief, the process of type registration is as follows. Each type is represented by a TypeNode structure. There are 2 storages of TypeNode structures: static_fundamental_type_nodes array for storing TypeNodes of static fundamental types and static_type_nodes_ht hash table for static non-fundamental types. Each GType is just the memory address of the corresponding TypeNode in case of non-fundamental types or index of TypeNode in static_fundamental_type_nodes in case of fundamental types. What happens to dynamic types - I don't know, please explain me if you can. The corresponding code resides in gtype_init function, responsible for initialization of the type system: http://git.gnome.org/browse/glib/tree/gobject/gtype.c#n4323.

enter image description here

GValue, GParamSpec and GObject are GTypes themselves, so they are registered as types.

GValue is meant used to register new type values through it, but how?.

GParameters and GParamSpec seem to be required for registering GObject type (not sure). How exactly it is done? What are the roles of each?

MOST IMPORTANTLY: What are the roles of GTypeValueTable, GTypeInfo and TypeData? TypeData is referrenced by TypeNode and contains GTypeValueTable as well as substructures BoxedData, ClassData, IFaceData, InstanceData (why Instance, aren't we registering type?). Moreover, they seems to duplicate each other, cause ALL of them contain references to base_init/finalize, class_init/finalize has a reference to GTypeValueTable.

So, GObject papas, if you're reading this, please, explain yourselves! Describe the purpose of those structures you use.

Forcible answered 12/2, 2013 at 19:42 Comment(0)
L
18

The only two of these that you really need to care about unless you're attempting to work on some very low level code are GValue and GParamType

I'll start with GParamType

GParamType is for used for registering a property with a GObject. Say, for example, I have a GObject subclass called Person, and I wanted it to have two properties: Name and Age. In the class_init function I would register these like so

{
    GParamSpec *pspec;

    . . .

    pspec = g_param_spec_string ("name", "Name", "The name of the person", "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
    g_object_class_install_property (object_class, PROP_NAME, pspec);

    pspec = g_param_spec_int ("age", "Age", "The age of the person", 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
    g_object_class_install_property (object_class, PROP_AGE, spec);

    . . .
}

Now you can call g_object_get or g_object_set on those properties and the system will know how to handle it

char *name;
int age;
g_object_set (G_OBJECT (person), "name", "Steve", "age", 37, NULL);
g_object_get (G_OBJECT (person), "name", &name, "age", &age, NULL);

g_print ("%s is %d years old\n", name, age);

// And because the type system knows when a property is a string, it knows how to give
// you a copy of the string, so you need to free it once you've finished with it
g_free (name);

The various parameters are explained here: GParamSpec There are GValue types for all the standard types: strings, bools, ints etc, and some other libraries such as GStreamer will register their own custom ones.

Outside of installing properties on GObjectClass you very rarely need to deal with GParamSpec. The two main occasions where they appear is in the GObjectClass set/get_property methods and the GObject notify signal. It is useful in the last case to detect which property has received the notify signal, by calling g_param_spec_get_name, but really it's better to use a more specific notify signal like so:

g_signal_connect (person, "notify::name", G_CALLBACK (name_changed_cb), NULL);
g_signal_connect (person, "notify::age", G_CALLBACK (age_changed_cb), NULL);

rather than

g_signal_connect (person, "notify", G_CALLBACK (something_changed_cb), NULL);

Sometimes you may want to create your own structures and use those for the properties. For example if I had

struct _PersonDetails {
    char *name;
    int age;
}

and instead of having two properties on the Person object, I wanted one called "details". The GLib type system does not know how to deal with my custom struct _PersonDetails so I would need to create a boxed type for it, so that it knew how to correctly copy/free the structure as it is passed around the Glib internals. And that is where GValue comes in.

GValue is for wrapping values of different types so they can be copied and freed correctly (if they need to be), and so that generic functions can be used.

For example, the GObjectClass method set_property has the prototype of

void set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)

This means that any type which can be represented by a GValue can be passed in and specific functions such as set_int_property, set_string_property, set_bool_property are not required.

It also means that the functions g_object_set and g_object_get know how to deal with the parameters that are passed in because it knows that the property "name" is registered to be a string type, and it has the functions necessary to copy/free that string.

More about GValue can be found here - Generic values

To register our custom struct _PersonDetails with the GLib type system we would create a custom Boxed type which told the system how to copy and free it. The details are here: Boxed Types

G_DEFINE_BOXED_TYPE (PersonDetails, person_details,
                     person_details_copy,
                     person_details_free)
. . .

static gpointer
person_details_copy (gpointer data)
{
    struct _PersonDetails *details = (struct _PersonDetails *)data;
    struct _PersonDetails *copy = g_new (struct _PersonDetails, 1);

    // We need to copy the string
    copy->name = g_strdup (details->name);
    copy->age = details->age;

    return (gpointer) copy;
}

static void
person_details_free (gpointer data)
{
    struct _PersonDetails *details = (struct _PersonDetails *)data;

    // name was allocated so it needs freed as well
    g_free (details->name);

    g_free (details);
}

Now we can register our type using

pspec = g_param_spec_boxed ("details", "Details", "The person's details", person_details_get_type (), G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_DETAILS, pspec);
Levan answered 13/2, 2013 at 7:48 Comment(4)
Best and most explicit explanation regarding this topic I read until now. +1Fogbow
the g_param_spec_gtype() call is wrong in this case: for boxed types you should use g_param_spec_boxed(); g_param_spec_gtype() is for properties storing the actual GType value. also, to register a boxed type you should use the G_DEFINE_BOXED_TYPE() macro, which generates the get_type() function for you.Pursuivant
@Pursuivant fixed the _spec_boxed call.Levan
@Levan thanks for your answer, it's really helpful and useful for me to understand gobject. And could you please explain how GTypeValueTable works? I'm confused about it. Sometimes it looks like virtual table in C++, but it only value handler function pointers inside.Eduction

© 2022 - 2024 — McMap. All rights reserved.