GObject 07: A class with properties.

14516 ワード

A good practice of Object-oriented Programming is to hide implementation details from the user.  As explained before, programmers are encouraged to use "private"fields.  However, if you want to let users access those fields, you could do this:
typedef {
    int foo;
} MyInstancePrivate;

int get_foo(MyInstance* instance) {
    MyInstancePrivate * priv = G_TYPE_INSTANCE_GET_PRIVATE(instance, MY_TYPE, MyInstancePrivate);
    return priv->foo;
}

void set_foo(MyInstance* instance, int new_foo) {
    MyInstancePrivate * priv = G_TYPE_INSTANCE_GET_PRIVATE(instance, MY_TYPE, MyInstancePrivate);
    priv->foo = new_foo;
}

These two functions are called "getter"and "setter".  Instead of directly modifying the data, getters and setters may do extra works like data validating, changing other members, computing the output or notifying other objects about this change.  Java programmers also use getters and setters pervasively.
Now let's introduce the concept of "property".  A class may have many "properties".  Properties can be got and set.  When got or set, it appears as if getters or setters are called.
.NET Languages have language-level properties.  The above program can be written in equivalent C# code as:
class Hello {
    private int _foo;
    public int foo {
        get {
            return _foo;
        } set {
            _foo = value;
        }
    }
}

Although Java does not have language-level "property"feature, it uses JavaBean convensions and reflection to simulate properties.
In GObject library, the GObject class allows user to add properties to their classes.
Full code here.  Don't read it now.
/* A subclass of GObject with a property. */

#include <stdio.h>
#include <glib-object.h>

#define PROPERTY_ID_OF_NAME 1
#define PROPERTY_ID_OF_NUMBER 2

typedef struct {
    GObject something_as_boilerplate;
    char *private_name;
    int private_number;
} myinstance_t;

typedef struct {
    GObjectClass something_as_boilerplate;
} myclass_t;

GType get_my_typeid();

void my_getter(GObject *object, guint property_id,
        GValue *value, GParamSpec *pspec) {
    myinstance_t *instance = G_TYPE_CHECK_INSTANCE_CAST(
            object,get_my_typeid(), myinstance_t);

    switch(property_id) {
        case PROPERTY_ID_OF_NAME:
            g_value_set_string(value,instance->private_name);
            break;
        case PROPERTY_ID_OF_NUMBER:
            g_value_set_int(value,instance->private_number);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object,property_id,pspec);
            break;
    }

}

void my_setter(GObject *object, guint property_id,
        const GValue *value, GParamSpec *pspec) {
    myinstance_t *instance = G_TYPE_CHECK_INSTANCE_CAST(
            object,get_my_typeid(), myinstance_t);

    switch(property_id) {
        case PROPERTY_ID_OF_NAME:
            g_free(instance->private_name);
            instance->private_name = g_value_dup_string(value);
            break;
        case PROPERTY_ID_OF_NUMBER:
            instance->private_number = g_value_get_int(value);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object,property_id,pspec);
            break;
    }
}

void my_instance_init_func(myinstance_t *instance, gpointer data) {
}

void my_class_init_func(myclass_t* klass, gpointer data) {
    G_OBJECT_CLASS(klass)->get_property = my_getter;
    G_OBJECT_CLASS(klass)->set_property = my_setter;

    GParamSpec *my_param_spec;

    // Property "name"
    my_param_spec = g_param_spec_string(
            "name",
            "Name property",
            "The name property of my class.  Can be used on constructing.",
            "All your property are belong to us.",
            G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT
            );
    g_object_class_install_property(
            G_OBJECT_CLASS(klass),
            PROPERTY_ID_OF_NAME,
            my_param_spec
            );

    // Property "number"
    my_param_spec = g_param_spec_int(
            "number",
            "Number property",
            "The number property of my class.",
            G_MININT,
            G_MAXINT,
            42,
            G_PARAM_READABLE | G_PARAM_WRITABLE
            );
    g_object_class_install_property(
            G_OBJECT_CLASS(klass),
            PROPERTY_ID_OF_NUMBER,
            my_param_spec
            );
}

GType get_my_typeid() {
    static my_type_id = 0;
    if(my_type_id==0) {
        GTypeInfo my_type_info = {
            sizeof(myclass_t),  //class_size;

            NULL,               //base_init;
            NULL,               //base_finalize;

            /* classed types, instantiated types */
            (GClassInitFunc)my_class_init_func, //class_init;
            NULL,               //class_finalize;
            NULL,               //class_data;

            /* instantiated types */
            sizeof(myinstance_t),//instance_size;
            0,                  //n_preallocs;
            (GInstanceInitFunc)my_instance_init_func, //instance_init;

            /* value handling */
            NULL,               //value_table;
        };

        my_type_id = g_type_register_static(
                G_TYPE_OBJECT,
                "MyClass",
                &my_type_info,
                0
                );
    }
    return my_type_id;
}


int main() {
    g_type_init();

    printf("Type id: %d
",get_my_typeid()); printf("Type name: %s
",g_type_name(get_my_typeid())); GValue *val = g_new0(GValue,1); // the first object myinstance_t *instance = (myinstance_t*)g_object_new(get_my_typeid(),NULL); g_value_init(val,G_TYPE_STRING); g_object_get_property(G_OBJECT(instance),"name",val); printf("Property \"name\": [%s]
",g_value_get_string(val)); g_value_unset(val); g_value_init(val,G_TYPE_INT); g_object_get_property(G_OBJECT(instance),"number",val); printf("Property \"number\": [%d]
",g_value_get_int(val)); g_value_unset(val); g_object_unref(instance); // the second object instance = (myinstance_t*)g_object_new(get_my_typeid(), "name","blahblah", "number",75, NULL); g_value_init(val,G_TYPE_STRING); g_object_get_property(G_OBJECT(instance),"name",val); printf("Property \"name\": [%s]
",g_value_get_string(val)); g_value_unset(val); g_value_init(val,G_TYPE_INT); g_object_get_property(G_OBJECT(instance),"number",val); printf("Property \"number\": [%d]
",g_value_get_int(val)); g_value_unset(val); g_value_init(val,G_TYPE_INT); g_value_set_int(val,81); g_object_set_property(G_OBJECT(instance),"number",val); g_value_unset(val); g_value_init(val,G_TYPE_INT); g_object_get_property(G_OBJECT(instance),"number",val); printf("Property \"number\": [%d]
",g_value_get_int(val)); g_value_unset(val); g_object_unref(instance); g_free(val); return 0; }

Let's concentrate on the class initializing function.
void my_class_init_func(myclass_t* klass, gpointer data) {
    G_OBJECT_CLASS(klass)->get_property = my_getter;
    G_OBJECT_CLASS(klass)->set_property = my_setter;

    GParamSpec *my_param_spec;

    // Property "name"
    my_param_spec = g_param_spec_string(
            "name",
            "Name property",
            "The name property of my class.  Can be used on constructing.",
            "All your property are belong to us.",
            G_PARAM_READABLE | G_PARAM_WRITABLE | G_PARAM_CONSTRUCT
            );
    g_object_class_install_property(
            G_OBJECT_CLASS(klass),
            PROPERTY_ID_OF_NAME,
            my_param_spec
            );

    // Property "number"
    my_param_spec = g_param_spec_int(
            "number",
            "Number property",
            "The number property of my class.",
            G_MININT,
            G_MAXINT,
            42,
            G_PARAM_READABLE | G_PARAM_WRITABLE
            );
    g_object_class_install_property(
            G_OBJECT_CLASS(klass),
            PROPERTY_ID_OF_NUMBER,
            my_param_spec
            );
}

A property is registered with g_object_class_install_property function.  It takes three parameters: the class struct, the property-id and a GParamSpec struct.  The GParamSpec struct my_param_spec contains informations about the property including its name, long name, discription, default value as well as access flags.
Each property need a unique integer identifies.
#define PROPERTY_ID_OF_NAME 1
#define PROPERTY_ID_OF_NUMBER 2

A getter and a setter function is required for this class.  They are virtual methods of GObject class.
void my_getter(GObject *object, guint property_id,
        GValue *value, GParamSpec *pspec) {
    myinstance_t *instance = G_TYPE_CHECK_INSTANCE_CAST(
            object,get_my_typeid(), myinstance_t);

    switch(property_id) {
        case PROPERTY_ID_OF_NAME:
            g_value_set_string(value,instance->private_name);
            break;
        case PROPERTY_ID_OF_NUMBER:
            g_value_set_int(value,instance->private_number);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object,property_id,pspec);
            break;
    }

}

void my_setter(GObject *object, guint property_id,
        const GValue *value, GParamSpec *pspec) {
    myinstance_t *instance = G_TYPE_CHECK_INSTANCE_CAST(
            object,get_my_typeid(), myinstance_t);

    switch(property_id) {
        case PROPERTY_ID_OF_NAME:
            g_free(instance->private_name);
            instance->private_name = g_value_dup_string(value);
            break;
        case PROPERTY_ID_OF_NUMBER:
            instance->private_number = g_value_get_int(value);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object,property_id,pspec);
            break;
    }
}

And they overrides the default get_property and set_property in the GObject class.  These are done in my_class_init_func:
    G_OBJECT_CLASS(klass)->get_property = my_getter;
    G_OBJECT_CLASS(klass)->set_property = my_setter;

Whenever a property is got, the get_property virtual function is called.  Then the property_id is passed in which is supposed to be identified.  A switch structure is used to delegate this property to their respective getter codes where the GValue *value parameter is supposed to be filled.  It is similar for the setter.
In the main() function, instances are created using g_object_new() function.
    instance = (myinstance_t*)g_object_new(get_my_typeid(),
            "name","blahblah",
            "number",75,
            NULL);

The first parameter is the type-id.  Then there are pairs of property-names and values.  The variable-length parameter list ends with a NULL.  The properties specified here will be initialized on creating.
From then on, you can get or set the properties using g_object_get_property and g_object_set_property.  You have to use GValue container to do so.  A GValue conatainer may contain a value of any type and it always know what type it is containing.  It is equivalent to QVariant if you have used Qt library for C++.
    GValue *val = g_new0(GValue,1);
    g_value_init(val,G_TYPE_INT);
    g_value_set_int(val,81);
    g_object_set_property(G_OBJECT(instance),"number",val);
    g_value_unset(val);

You have to initialize the memory block of the GValue instance with '\0's.  You need to use g_value_init to specify what type it contains and use g_value_unset when you no longer use it.
In the GParamSpec struct passed into g_object_class_install_property, flags can be used to specify whether it is readable or writable.  There are G_PARAM_READABLE and G_PARAM_WRITABLE flags.  G_PARAM_CONSTRUCT means that if you do not initialize the property when the instance is created, it will be initialized to the default value.  It is not required to have G_PARAM_CONSTRUCT in order to initialize it in g_object_new.  You can do this as long as it is writable.  And G_PARAM_CONSTRUCT_ONLY constraint that this property can only be set when the object is constructed.