GObject 参考手册:概念:基类 GObject:对象属性

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_property (implemented in gobject.c).
GObject的其中一个漂亮特性就是它那为对象属性准备的通用get/set机制。当一个对象被实例化以后,对象的类初始化处理将用g_object_class_install_property来注册对象的属性(由gobject.c中实现)。

The best way to understand how object properties work is by looking at a real example on how it is used:
理解对象属性是如何工作的最好就是看下面的例子:

/************************************************/
/* Implementation                               */
/************************************************/
 
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);
}
 
/************************************************/
/* Use                                          */
/************************************************/
 
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);

The client code just 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 bar’s class_init handler. If so, it calls object_set_property which first walks the class hierarchy, from bottom, most derived type, to top, fundamental type to find the class which registered that property. It then tries to convert the user-provided GValue into a GValue whose type if that of the associated property.
g_object_set_property先确保相应名称的属性已经在bar的class_init处理函数中被注册。如果是的话,它依次调用类继承关系中的object_set_property,从底至顶,基础类型用来找到注册了这个属性的类。接着它尝试转换用户提供的GValue到属性所关联的 GValue。

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 which matches and conversion will be carried out if needed.
如果用户提供了一个有符号的字符GValue,就像这里所示,如果对象的属性被注册为一个无符号的整型,g_value_transform将会试着转换输入的有符号的字符到一个无符号的整型。当然,转换是否成功取取决于可用的转换函数。实际上,如果需要的时候,总会有相关的转换函数可以用。

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.
在转型以后,GValue将被g_param_value_validata来验证,以确保用户保存在GValue中的数据吻合由属性的 GParamSpea所描述的字符特性。在这里,我们在class_init里提供的GParamSpec有一个验证函数来确保GValue包含了一个代表最小和最大边界的GParamSpec。在上面的例子中,客户端的GValue并没有尊重规范(它设置为了11,而最大值是10)。因为这样,所了 g_object_set_property函数将返回一个错误。

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 Foo did override this method, the code path would jump to foo_set_property after having retrieved from the GParamSpec the param_id which had been stored by g_object_class_install_property.
如果用户的GValue已经被设置成了一个可用的值,g_object_set_property将处理一下呼叫至对象的set_property的类方法。在这里,因为我们在Foo的实现代码中并没有重载这个函数,代码路径将会跳到foo_set_property在收到 g_object_class_install_property存储了GParamSpec的param_id后。

Once the property has been set by the object’s set_property class method, the code path returns to g_object_set_property which calls g_object_notify_queue_thaw. This function 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.
一时已经用对象的set_property类方法来设置好属性以后,代码路径将调用g_object_nofity_queue_thaw使返回到 g_object_set_property 。这个函数确保“notify”信号”已经在对象实例完成改变属性后被发出,除非通知台已经被g_object_free_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. Signal can only be delayed by the notification freezing mechanism.
g_object_thaw_nofity可以被用来重新启用通过“notify”信号的属性修改的通知中心。这是非常重要的,记住当属性被改变时通知中心是否被冻结,“notify”信号将会当在属性改变的一睡意由通知中心所发出:没有属性改变会因“notify”所信号。只有通过通知中心的冻结机制才能使信号发身被延误。

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.
这听起来像是一个无聊的任务每次设置GValues当我想需要一个属性时。实际上我们仅仅很少这样做。g_object_set_property和g_object_get_property一般是用来语言绑定的。对应用程序来说,有一个更简单的方法,在下面描述。

Accessing multiple properties at once
同时修改多个属性

It is interesting to note that the g_object_set and g_object_set_valist (vararg version) functions can be used to set multiple properties at once. The client code shown above can then be re-written as:
我想这很有趣,我们可以通过g_object_set和g_object_set_valist函数来同时设置多个属性值。客户端代码可以被重写为:

MamanBar *foo;
foo = /* */;
g_object_set (G_OBJECT (foo),
              "papa-number", 2, 
              "maman-name", "test", 
              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.
这个节省了我们管理用g_object_set_property来处理GValue的时间。在被修改时这个代码同样会触发每个属性。

Of course, the _get versions are also available: g_object_get and g_object_get_valist (vararg version) can be used to get numerous properties at once.
当然,_get的版本同样是存在的:g_object_get和g_object-get_valist可以用来一次性得到很多属性。

These high level functions have one drawback – they don’t provide a return result. One should pay attention to the argument types and ranges when using them. A know source of errors is to e.g. pass a gfloat instead of a gdouble and thus shifting all subsequent parameters by four bytes. Also forgetting the terminating NULL will lead to unexpected behaviour.
这些高级的方法有一个缺点──它们不提供一个返回值。在使用它们时,你需要注意这些参数类型和范围 。一个已知的错误,例如通过 gfloat 而不是 gdouble 传递,其后的参数会使用后4字节,这样会导致不可预料的行为。

Really attentive readers now understand 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. Of course, the “notify” signal will be emitted for each property set.
如果你认真看这章的话,现在你应该已经知道了g_object_new,g_object_newv和g_object_new_valist是如何工作的:它们解析用户提供的变量数目和参数并当对象成功的创建以后,用这些参数调用g_object_set。当然,“notify”信号同样会在每个属性改变后发射。

Leave a Reply

Your email address will not be published. Required fields are marked *