Справочное описание GObject |
---|
Одной из замечательных особенностей 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) {}.