原文:http://library.gnome.org/devel/gobject/2.14/gobject-properties.html
GObject的其中一个漂亮特性就是它那为对象属性准备的通用get/set机制。当一个对象被实例化以后,对象的类初始化处理将用g_object_class_install_property来注册对象的属性(由gobject.c中实现)。
理解对象属性是如何工作的最好就是看下面的例子:
/************************************************/ /* 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);
上面的例子看起来应该是简单的,但是很多事情发生了:
g_object_set_property先确保相应名称的属性已经在bar的class_init处理函数中被注册。如果是的话,它依次调用类继承关系中的object_set_property,从底至顶,基础类型用来找到注册了这个属性的类。接着它尝试转换用户提供的GValue到属性所关联的GValue。
如果用户提供了一个有符号的字符GValue,就像这里所示,如果对象的属性被注册为一个无符号的整型,g_value_transform将会试着转换输入的有符号的字符到一个无符号的整型。当然,转换是否成功取取决于可用的转换函数。实际上,如果需要的时候,总会有相关的转换函数可以用。
在转型以后,GValue将被g_param_value_validata来验证,以确保用户保存在GValue中的数据吻合由属性的GParamSpea所描述的字符特性。在这里,我们在class_init里提供的GParamSpec有一个验证函数来确保GValue包含了一个代表最小和最大边界的GParamSpec。在上面的例子中,客户端的GValue并没有尊重规范(它设置为了11,而最大值是10)。因为这样,所了g_object_set_property函数将返回一个错误。
如果用户的GValue已经被设置成了一个可用的值,g_object_set_property将处理一下呼叫至对象的set_property的类方法。在这里,因为我们在Foo的实现代码中并没有重载这个函数,代码路径将会跳到foo_set_property在收到g_object_class_install_property存储了GParamSpec的param_id后。
一时已经用对象的set_property类方法来设置好属性以后,代码路径将调用g_object_nofity_queue_thaw使返回到g_object_set_property 。这个函数确保“notify”信号”已经在对象实例完成改变属性后被发出,除非通知台已经被g_object_free_notify所冻洁。
g_object_thaw_nofity可以被用来重新启用通过“notify”信号的属性修改的通知中心。这是非常重要的,记住当属性被改变时通知中心是否被冻结,“notify”信号将会当在属性改变的一睡意由通知中心所发出:没有属性改变会因“notify”所信号。只有通过通知中心的冻结机制才能使信号发身被延误。
这听起来像是一个无聊的任务每次设置GValues当我想需要一个属性时。实际上我们仅仅很少这样做。g_object_set_property和g_object_get_property一般是用来语言绑定的。对应用程序来说,有一个更简单的方法,在下面描述。
同时修改多个属性
我想这很有趣,我们可以通过g_object_set和g_object_set_valist函数来同时设置多个属性值。客户端代码可以被重写为:
MamanBar *foo; foo = /* */; g_object_set (G_OBJECT (foo), "papa-number", 2, "maman-name", "test", NULL);
这个节省了我们管理用g_object_set_property来处理GValue的时间。在被修改时这个代码同样会触发每个属性。
当然,_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.
如果你认真看这章的话,现在你应该已经知道了g_object_new,g_object_newv和g_object_new_valist是如何工作的:它们解析用户提供的变量数目和参数并当对象成功的创建以后,用这些参数调用g_object_set。当然,“notify”信号同样会在每个属性改变后发射。