Object properties: GObject Reference Manual

## Object properties

One of GObject's nice features is its generic get/set mechanism for object properties. When an object is instantiated, the object's `class_init` handler should be used to register the object's properties with `g_object_class_install_properties`.

The best way to understand how object properties work is by looking at a real example of how it is used:

    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
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    /************************************************/
    /* Implementation                               */
    /************************************************/

    typedef enum
    {
      PROP_FILENAME = 1,
      PROP_ZOOM_LEVEL,
      N_PROPERTIES
    } ViewerFileProperty;

    static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };

    static void
    viewer_file_set_property (GObject      *object,
                              guint         property_id,
                              const GValue *value,
                              GParamSpec   *pspec)
    {
      ViewerFile *self = VIEWER_FILE (object);

      switch ((ViewerFileProperty) property_id)
        {
        case PROP_FILENAME:
          g_free (self->filename);
          self->filename = g_value_dup_string (value);
          g_print ("filename: %s\n", self->filename);
          break;

        case PROP_ZOOM_LEVEL:
          self->zoom_level = g_value_get_uint (value);
          g_print ("zoom level: %u\n", self->zoom_level);
          break;

        default:
          /* We don't have any other property... */
          G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
          break;
        }
    }

    static void
    viewer_file_get_property (GObject    *object,
                              guint       property_id,
                              GValue     *value,
                              GParamSpec *pspec)
    {
      ViewerFile *self = VIEWER_FILE (object);

      switch ((ViewerFileProperty) property_id)
        {
        case PROP_FILENAME:
          g_value_set_string (value, self->filename);
          break;

        case PROP_ZOOM_LEVEL:
          g_value_set_uint (value, self->zoom_level);
          break;

        default:
          /* We don't have any other property... */
          G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
          break;
        }
    }

    static void
    viewer_file_class_init (ViewerFileClass *klass)
    {
      GObjectClass *object_class = G_OBJECT_CLASS (klass);

      object_class->set_property = viewer_file_set_property;
      object_class->get_property = viewer_file_get_property;

      obj_properties[PROP_FILENAME] =
        g_param_spec_string ("filename",
                             "Filename",
                             "Name of the file to load and display from.",
                             NULL  /* default value */,
                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);

      obj_properties[PROP_ZOOM_LEVEL] =
        g_param_spec_uint ("zoom-level",
                           "Zoom level",
                           "Zoom level to view the file at.",
                           0  /* minimum value */,
                           10 /* maximum value */,
                           2  /* default value */,
                           G_PARAM_READWRITE);

      g_object_class_install_properties (object_class,
                                         N_PROPERTIES,
                                         obj_properties);
    }

    /************************************************/
    /* Use                                          */
    /************************************************/

    ViewerFile *file;
    GValue val = G_VALUE_INIT;

    file = g_object_new (VIEWER_TYPE_FILE, NULL);

    g_value_init (&val, G_TYPE_UINT);
    g_value_set_char (&val, 11);

    g_object_set_property (G_OBJECT (file), "zoom-level", &val);

    g_value_unset (&val);

The client code above looks simple but a lot of things happen under the hood:

`g_object_set_property` first ensures a property with this name was registered in file's `class_init` handler. If so it walks the class hierarchy, from bottom-most most-derived type, to top-most fundamental type to find the class which registered that property. It then tries to convert the user-provided GValue into a GValue whose type is that of the associated property.

If the user provides a signed char GValue, as is shown here, and if the object's property was registered as an unsigned int, `g_value_transform` will try to transform the input signed char into an unsigned int. Of course, the success of the transformation depends on the availability of the required transform function. In practice, there will almost always be a transformation [2] which matches and conversion will be carried out if needed.

After transformation, the GValue is validated by `g_param_value_validate` which makes sure the user's data stored in the GValue matches the characteristics specified by the property's GParamSpec. Here, the GParamSpec we provided in `class_init` has a validation function which makes sure that the GValue contains a value which respects the minimum and maximum bounds of the GParamSpec. In the example above, the client's GValue does not respect these constraints (it is set to 11, while the maximum is 10). As such, the `g_object_set_property` function will return with an error.

If the user's GValue had been set to a valid value, `g_object_set_property` would have proceeded with calling the object's `set_property` class method. Here, since our implementation of ViewerFile did override this method, execution would jump to `viewer_file_set_property` after having retrieved from the GParamSpec the param_id [3] which had been stored by `g_object_class_install_property`.

Once the property has been set by the object's `set_property` class method, execution returns to `g_object_set_property` which makes sure that the "notify" signal is emitted on the object's instance with the changed property as parameter unless notifications were frozen by `g_object_freeze_notify`.

`g_object_thaw_notify` can be used to re-enable notification of property modifications through the “notify” signal. It is important to remember that even if properties are changed while property change notification is frozen, the "notify" signal will be emitted once for each of these changed properties as soon as the property change notification is thawed: no property change is lost for the "notify" signal, although multiple notifications for a single property are compressed. Signals can only be delayed by the notification freezing mechanism.

It sounds like a tedious task to set up GValues every time when one wants to modify a property. In practice one will rarely do this. The functions `g_object_set_property` and `g_object_get_property` are meant to be used by language bindings. For application there is an easier way and that is described next.

### Accessing multiple properties at once

It is interesting to note that the `g_object_set` and `g_object_set_valist` (variadic version) functions can be used to set multiple properties at once. The client code shown above can then be re-written as:

    1
    2
    3
    4
    5
    6
    ViewerFile *file;
    file = /* */;
    g_object_set (G_OBJECT (file),
                  "zoom-level", 6,
                  "filename", "~/some-file.txt",
                  NULL);

This saves us from managing the GValues that we were needing to handle when using `g_object_set_property`. The code above will trigger one notify signal emission for each property modified.

Equivalent `_get` versions are also available: `g_object_get` and `g_object_get_valist` (variadic version) can be used to get numerous properties at once.

These high level functions have one drawback — they don't provide a return value. One should pay attention to the argument types and ranges when using them. A known source of errors is to pass a different type from what the property expects; for instance, passing an integer when the property expects a floating point value and thus shifting all subsequent parameters by some number of bytes. Also forgetting the terminating `NULL` will lead to undefined behaviour.

This explains how `g_object_new`, `g_object_newv` and `g_object_new_valist` work: they parse the user-provided variable number of parameters and invoke `g_object_set` on the parameters only after the object has been successfully constructed. The "notify" signal will be emitted for each property set.

[2] Its behaviour might not be what you expect but it is up to you to actually avoid relying on these transformations.

[3] It should be noted that the param_id used here need only to uniquely identify each GParamSpec within the ViewerFileClass such that the switch used in the set and get methods actually works. Of course, this locally-unique integer is purely an optimization: it would have been possible to use a set of if (strcmp (a, b) == 0) {} else if (strcmp (a, b) == 0) {} statements.