The GObject base class
Object memory management
Gobject的内存管理相关的API很复杂,但其目标是提供一个基于引用计数的灵活的内存管理模式。
下面我们来介绍一下,与管理引用计数相关的函数。
Reference Count
函数g_object_ref和g_object_unref的作用分别是增加和减少引用计数。g_clear_object函数是对g_object_unref的封装。
在调用g_object_new时,引用计数是1,1代表到目前为止使用此函数的使用者是此对象的唯一使用者,当引用计数是0时,对象的类结构体中的disopose()和finalize()会被调用,之后会调用g_type_free_instance()来释放对象实例结构体所占用的空间或者返回空间到此类型对象的对象池。如果对象的最后一个实例被释放,那么,对象的类结构体也会被销毁。
GObject销毁流程如下表:
Weak Reference
weak reference 的作用是监视对象的终止(finalization),g_object_weak_ref()会添加一个监视回调函数到对象,回调函数不会使对象的引用计数有任何变化,它只是在对象运行dispose的时候执行。
Reference counts and cycles
GObject的销毁过程分为两个阶段,第一个阶段是在dispose函数中,dispose()会释放所有对其它成员对象的引用。第二个阶段是在finalize函数中,finalize()会释放对象本身所占用空间。在调用dispose后,调用finalize之前,对象的方法还能正常执行。
销毁过程分为两个阶段有益于打破循环引用。当检测到循环引用时,为了正常进行销毁工作,调用g_object_run_dipose就能打破循环。dispose能被多次执行,finalize只会执行一次。现在我们举例对这几句话进行说明。假如,对象A和对象B相互引用,现在我们检测到这样的情况,那么,现在需要销毁这两个对象,该怎么办呢?一种方式就是应用程序在操作释放一个对象时,调用g_object_run_dispose。
如果对象A全部释放所有对其他引用,当然也会释放对对象B的引用。如果这是最后一个对对象B的引用,那么A释放对B的引用,会导致unref 函数执行对象B的dispose, 进而导致B对象释放对对象A的引用,如果这也是对对象A的最后一个引用,对象B对对象A的引用的释放会导致第二次执行A的dispose。
Object properties
GObject的一个功能是能够通过set_property/get_property函数对对象属性进行操作。首先我们先举例说明属性如何定义的,以及在应用程序中如何使用。
首先,看一下定义。
// 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);
之前,在介绍interface时,有个完整的例子使用过属性。Gobject tutorial 六-CSDN博客
现在我们介绍一下属性。
Properties
属性功能是由Gobject系统提供的。属性本身都是各种类型的数据,换句话说,对于单一的属性,它其实是一个类型的数据,可以是char *,也可以int,等等类型。属性是由对象实例维护,当然,这些对象实例都继承GObject。属性是按照名字来进行访问的,且一个实例中的对象,能够被其他实例访问。问题来了,这个“共享“是怎么做到的?我们后续会进行讨论。
在GObject中有很多方式可以设置和获取属性。
- set方法有g_object_new、g_object_set, 后者是在应用中常用的方式。
- get方法中应用最常用的方式是g_object_get.
GtkWindow *win;
win = g_object_new (GTK_TYPE_WINDOW, \
"title", "Hello", \
"default-width", 800, \
"default-height", 600, NULL);
另一个使用方式如下,
GtkWindow *win;
win = g_object_new (GTK_TYPE_WINDOW, NULL);
g_object_set (win, "title", "Good bye", NULL);
那么,如何获取呢,
GtkWindow *win;
char *title;
int width, height;
win = g_object_new (GTK_TYPE_WINDOW, \
"title", "Hello", \
"default-width", 800, \
"default-height", 600, NULL);
g_object_get (win, "title", &title, \
"default-width", &width, \
"default-height", &height, NULL);
g_print ("%s, %d, %d\n", title, width, height);
g_free (title);
GParamSpec
说到属性,我们不得不对GParamSpec进行说明。GParamSpec是一个fundamental object, 它与GObject并无父子关系,它的作用是保存参数的信息,当然此处的参数也可以是属性。
我们看看它在属性注册过程中的作用。
/*gtk 4.8.2 demo3widget.c*/
static void
demo3_widget_class_init (Demo3WidgetClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
......
object_class->dispose = demo3_widget_dispose;
object_class->set_property = demo3_widget_set_property;
object_class->get_property = demo3_widget_get_property;
......
g_object_class_install_property (object_class, PROP_PAINTABLE,
g_param_spec_object ("paintable", "Paintable", "Paintable",
GDK_TYPE_PAINTABLE,
G_PARAM_READWRITE));
g_object_class_install_property (object_class, PROP_SCALE,
g_param_spec_float ("scale", "Scale", "Scale",
0.0, 10.0, 1.0,
G_PARAM_READWRITE));
......
}
注册 属性是通过函数g_object_class_install_property完成的,新类型在注册时,会实现自己的set_property/get_property函数,如上面的demo3_widget_set_property/demo3_widget_get_property。
g_object_class_install_property函数的声明如下,
void g_object_class_install_property (GObjectClass *oclass,
guint property_id,
GParamSpec *pspec)
既然在注册属性过程中,GparamSpec是必须的,那么,就有必要对生成GParamSpec的函数进行说明,我们以上例中的g_param_spec_float为例,看看Glib的说明。
/**
* g_param_spec_float:
* @name: canonical name of the property specified
* @nick: (nullable): nick name for the property specified
* @blurb: (nullable): description of the property specified
* @minimum: minimum value for the property specified
* @maximum: maximum value for the property specified
* @default_value: default value for the property specified
* @flags: flags for the property specified
*
* Creates a new #GParamSpecFloat instance specifying a %G_TYPE_FLOAT property.
*
* See g_param_spec_internal() for details on property names.
*
* Returns: (transfer full): a newly created parameter specification
*/
GParamSpec*
g_param_spec_float (const gchar *name,
const gchar *nick,
const gchar *blurb,
gfloat minimum,
gfloat maximum,
gfloat default_value,
GParamFlags flags)
Overriding set_property and get_property class methods
重写set_property 、get_property的过程如下:
对于不同的对象实例,属性值是不同的,因此,属性值是存储在对象的实例中的。具体这是怎么做的呢。我们以上例中的demo3_widget_set_property为例进行说明。
static void
demo3_widget_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
Demo3Widget *self = DEMO3_WIDGET (object);
switch (prop_id)
{
case PROP_PAINTABLE:
g_clear_object (&self->paintable);
self->paintable = g_value_dup_object (value);
gtk_widget_queue_resize (GTK_WIDGET (object));
break;
case PROP_SCALE:
self->scale = g_value_get_float (value);
gtk_widget_queue_resize (GTK_WIDGET (object));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
可以看到需要设置的属性值value,保存在self->paintable、self->scale中。Demo3Widget的定义如下,
struct _Demo3Widget
{
GtkWidget parent_instance;
GdkPaintable *paintable;
float scale;
GtkWidget *menu;
};
Notify signal
还有一个重要的问题就是,当设置属性后,GObject会发出“notify"信号。如果你在程序中给信号链接了处理函数,你就能进行后续操作。notify信号在链接处理函数时,是有格式要求的。如下:
g_signal_connect (G_OBJECT (d1), "notify::value", G_CALLBACK (notify_cb), NULL);
上面示例中“notify::value”中的value需要替换成属性名。如果不设置value,那么,对象的任何属性被设置,回调函数都会执行。 注意,我们这里说,当属性被设置时,会发出notify 信号,那么,问题来了,我为属性设置相同的值,会不会有notify信号呢?答案是,会有的。那如果我想只在信号值发生改变时,才受到notify信号,该怎么办呢?这种情况下,首先我们在调用函数生成GParamSpec时,为函数设置属性G_PARAM_EXPLICIT_NOTIFY标记,具体来说,对于上面提到的函数
g_param_spec_float为例,设置其最后的参数flag为G_PARAM_EXPLICIT_NOTIFY。其次需要在属性发生变化时,手动调用函数g_object_notify_by_pspec来发送notify 信号。