Свойства объекта

Одной из замечательных особенностей GObject является родной механизм установки/получения свойств для объекта. Когда объект инстанциирован, объектный обработчик class_init должен использоваться для регистрации свойств объекта с помощью g_object_class_install_property (реализована в gobject.c).

Лучший способ понять как работают свойства объекта - посмотреть реальный пример их использования:

/************************************************/ /* Реализация */ /************************************************/ enum { MAMAN_BAR_CONSTRUCT_NAME = 1, MAMAN_BAR_PAPA_NUMBER, }; static void maman_bar_instance_init (GTypeInstance *instance, gpointer g_class) { MamanBar *self = (MamanBar *)instance; } static void maman_bar_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { MamanBar *self = (MamanBar *) object; switch (property_id) { case MAMAN_BAR_CONSTRUCT_NAME: { g_free (self->priv->name); self->priv->name = g_value_dup_string (value); g_print ("maman: %s\n",self->priv->name); } break; case MAMAN_BAR_PAPA_NUMBER: { self->priv->papa_number = g_value_get_uchar (value); g_print ("papa: %u\n",self->priv->papa_number); } break; default: /* We don't have any other property... */ G_OBJECT_WARN_INVALID_PROPERTY_ID(object,property_id,pspec); break; } } static void maman_bar_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { MamanBar *self = (MamanBar *) object; switch (property_id) { case MAMAN_BAR_CONSTRUCT_NAME: { g_value_set_string (value, self->priv->name); } break; case MAMAN_BAR_PAPA_NUMBER: { g_value_set_uchar (value, self->priv->papa_number); } break; default: /* We don't have any other property... */ G_OBJECT_WARN_INVALID_PROPERTY_ID(object,property_id,pspec); break; } } static void maman_bar_class_init (gpointer g_class, gpointer g_class_data) { GObjectClass *gobject_class = G_OBJECT_CLASS (g_class); MamanBarClass *klass = MAMAN_BAR_CLASS (g_class); GParamSpec *pspec; gobject_class->set_property = maman_bar_set_property; gobject_class->get_property = maman_bar_get_property; pspec = g_param_spec_string ("maman-name", "Maman construct prop", "Set maman's name", "no-name-set" /* default value */, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); g_object_class_install_property (gobject_class, MAMAN_BAR_CONSTRUCT_NAME, pspec); pspec = g_param_spec_uchar ("papa-number", "Number of current Papa", "Set/Get papa's number", 0 /* minimum value */, 10 /* maximum value */, 2 /* default value */, G_PARAM_READWRITE); g_object_class_install_property (gobject_class, MAMAN_BAR_PAPA_NUMBER, pspec); } /************************************************/ /* Использование */ /************************************************/ GObject *bar; GValue val = {0,}; bar = g_object_new (MAMAN_TYPE_SUBBAR, NULL); g_value_init (&val, G_TYPE_CHAR); g_value_set_char (&val, 11); g_object_set_property (G_OBJECT (bar), "papa-number", &val);

Код клиента продемонстрированный выше прост, но большинство вещей происходит скоытно:

g_object_set_property убеждается что свойство с таким названием было зарегистрировано обработчиком class_init в данной области. Если это так, она вызывает object_set_property которая обходит иерархию класса, от основания большинства производных типов, до вершины базового типа в поиске класса который зарегистрировал это свойство. Затем она пытается конвертировать обеспеченную пользователем GValue в GValue чей тип связан с этим свойством.

Если пользователь обеспечил signed char GValue, как показано здесь, и если свойство объекта было зарегистрировано как unsigned int, g_value_transform попытается преобразовать введённый signed char в unsigned int. Конечно успех преобразования зависит от возможностей преобразующей функции. На практике, почти всегда будет соответствующее преобразование [6] и преобразование будет перенесено если необходимо.

После преобразования, GValue утверждается с помощью g_param_value_validate которая проверяет чтобы пользовательские данные сохранённые в GValue соответствовали характеристикам определённым GParamSpec свойства. Здесь, GParamSpec мы обеспечили в функции class_init имеющей функцию проверки допустимости значений содержащихся в GValue, которые придерживаются минимальных и максимальных границ GParamSpec. В примере выше, GValue клиента не придерживается этих ограничений (оно установлено в 11, тогда как максимум равен 10). Поэтому функция g_object_set_property вернёт ошибку.

Если пользовательское GValue установлено в допустимое значение, g_object_set_property продолжит процесс вызова объектного метода класса set_property. В примере, так как наша реализация Foo отменяла этот метод, код перейдёт в foo_set_property найдя GParamSpec с param_id [7] который был сохранён с помощью g_object_class_install_property.

Как только свойство было установлено объектным методом класса set_property, код возвращается в g_object_set_property которая вызывает g_object_notify_queue_thaw. Эта функция убеждается что сигнал "notify" произошёл в экземпляре объекта с изменённым свойством в качестве параметра, если уведомления не были заморожены g_object_freeze_notify.

g_object_thaw_notify может использоваться для нового уведомления изменения свойства через сигнал "notify". Важно помнить что даже если свойства изменены в то время как уведомление изменения свойств заморожено, сигнал "notify" издаётся один раз для каждого из изменённых свойств, как только уведомление изменения свойства разморожено: ни одно изменение свойства не теряется для сигнала "notify". Сигнал может быть отсрочен с помощью механизма заморозки уведомления.

Похоже на утомительную задачу устанавливать GValues каждый раз когда необходимо изменить свойство. На практике, очень редко придётся это делать. Функции g_object_set_property и g_object_get_property предназначены для использования языковыми привязками. Для приложений есть более простой способ и он описан далее.

Одновременный доступ к множеству свойств

Интересно отметить что g_object_set и g_object_set_valist (vararg версия) могут использоваться для установки множества свойств одновременно. Код клиента показанный выше можно переписать как:

MamanBar *foo; foo = /* */; g_object_set (G_OBJECT (foo), "papa-number", 2, "maman-name", "test", NULL);

Это оберегает нас от управления GValues который мы должны были обработать используя g_object_set_property. Код выше переключает одну эмиссию сигнала уведомления для каждого изменяемого свойства.

Конечно, версия _get также доступна: g_object_get и g_object_get_valist (vararg версия) могут использоваться для получения множества свойств сразу.

Эти функции высокого уровня имеют один недостаток - они не обеспечивают возвращаемый результат. Нужно обращать внимание на типы аргументов и диапазон их использования. Известный источник ошибок например помещение gfloat вместо gdouble и таким образом смещение всех последующих параметров на четыре байта. Также отсутствие завершающего NULL может привести к неожиданному поведению.

Внимательные читатели теперь понимают как работают g_object_new, g_object_newv и g_object_new_valist: они анализируют количество пользовательских переменных и вызывают g_object_set для параметров только после полного проектирования объекта. Естественно, сигнал "notify" издаётся для каждого установленного свойства.



[6] Это может быть не то что вам нужно, но это позволяет вам решать полагаться ли на эти преобразования.

[7] Должно быть отмечено, что используемый здесь param_id уникален для идентификации каждого GParamSpec внутри FooClass так что переключение используемое в методах установка и получение фактически работает. Конечно, эти локальные уникальные целочисленные - просто оптимизация: возможно было бы использовать набор условий if (strcmp (a, b) == 0) {} else if (strcmp (a, b) == 0) {}.