GtkAboutDialog中的一个陷阱

来源:互联网 发布:mac java开发必备软件 编辑:程序博客网 时间:2024/05/18 13:09


GTK+学习笔记4 Comments

       众所周知,在一个GtkAboutDialog控件中,是可以用gtk_about_dialog_set_logo来设置logo的,其函数原型如下:

?
1
2
void               gtk_about_dialog_set_logo           (GtkAboutDialog *about,
                                                         GdkPixbuf *logo);

       显而易见,logo在这里是以GdkPixbuf控件的形式插入到GtkAboutDialog控件about中的,然而这里却暗藏了一个陷阱,而这个陷阱,有可能导致严重的内存泄漏。

       我们不妨以一下一段代码为例(编译为test_about_dialog):

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <stdlib.h>
#include <stdio.h>
#include <gtk/gtk.h>
 
void show_about_dialog(GtkWidget *button);
 
int main(int argc,char**argv)
{
    GtkWidget *window;
    GtkWidget *button;
     
    gtk_init(&argc,&argv);
 
    window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
     
    button=gtk_button_new_with_label("Click me!");
    gtk_container_add(GTK_CONTAINER(window),button);
     
    g_signal_connect(G_OBJECT(button),"clicked",G_CALLBACK(show_about_dialog),NULL);
    g_signal_connect(G_OBJECT(window),"destroy",G_CALLBACK(gtk_main_quit),NULL);
 
    gtk_widget_show_all(window);
     
    gtk_main();
 
    return0;
}
 
voidshow_about_dialog(GtkWidget *button)
{
    GtkWidget *about_dialog;
    GdkPixbuf *logo;
     
    about_dialog=gtk_about_dialog_new();
    logo=gdk_pixbuf_new_from_file("./test.png",NULL);
    gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(about_dialog),logo);
     
    gtk_dialog_run(GTK_DIALOG(about_dialog));
 
    gtk_widget_destroy(about_dialog);
 
    return;
 
}

       程序运行以后,出来一个窗体,这时我们打开系统监视器(如果你熟悉命令行的话,用top命令也可以),这时可以看到test_about_dialog运行时占用了约1.4M的内存:

       这时我们点一下"Click me"按钮,test_about_dialog占用的内存量上升到约3.9M:

       此时,我们关闭AboutDialog,然后再次点击"click me"按钮以打开AboutDialog,此时,test_about_dialog占用的内存量已经上升到了5.9M:

       多次重复这一操作,test_about_dialog占用的内存会越来越多,并且,每次关闭AboutDialog以后,内存占用量并不降低,显然,AboutDialog中出现了内存泄漏。

       而事实上,关键就在于这句代码:

?
1
gtk_widget_destroy(about_dialog);

       按照惯例,这句代码将释放about_dialog即其子控件所占用的所有资源,然而事实呢?

       我们可以看一下gtk_widget_destroy的代码:

?
1
2
3
4
5
6
7
void
gtk_widget_destroy (GtkWidget *widget)
{
  g_return_if_fail (GTK_IS_WIDGET (widget));
 
  gtk_object_destroy ((GtkObject*) widget);
}

        显然,它调用了gtk_object_destroy,而gtk_object_destroy的代码如下:

?
1
2
3
4
5
6
7
8
9
void
gtk_object_destroy (GtkObject *object)
{
  g_return_if_fail (object != NULL);
  g_return_if_fail (GTK_IS_OBJECT (object));
   
  if(!(GTK_OBJECT_FLAGS (object) & GTK_IN_DESTRUCTION))
    g_object_run_dispose (G_OBJECT (object));
}

         而g_object_run_dispose代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
void
g_object_run_dispose (GObject *object)
{
  g_return_if_fail (G_IS_OBJECT (object));
  g_return_if_fail (object->ref_count > 0);
 
  g_object_ref (object);
  TRACE (GOBJECT_OBJECT_DISPOSE(object,G_TYPE_FROM_INSTANCE(object), 0));
  G_OBJECT_GET_CLASS (object)->dispose (object);
  TRACE (GOBJECT_OBJECT_DISPOSE_END(object,G_TYPE_FROM_INSTANCE(object), 0));
  g_object_unref (object);
}

         而GtkAboutDialog的dispose函数继承自GtkDialog,而GtkDialog的dispose函数又继承自GtkWindow,而GtkWindow的dispose函数如下所示:

?
1
2
3
4
5
6
7
8
9
10
staticvoid
gtk_window_dispose (GObject *object)
{
  GtkWindow *window = GTK_WINDOW (object);
 
  gtk_window_set_focus (window, NULL);
  gtk_window_set_default (window, NULL);
 
  G_OBJECT_CLASS (gtk_window_parent_class)->dispose (object);
}

         事实上,dispose函数将会自底向上一层层地执行下去,那究竟是哪个语句最终导致资源被释放呢?

         其实就是g_object_run_dispose中的这个语句:

?
1
g_object_unref (object);

         他将使得object的ref_count降到零,进而调用dispose函数以及destroy函数,而真正负责释放资源的,是destroy函数。由destroy函数的特点知,该函数将会自底向上一层层地执行。而实现释放其子控件占用的资源的,是GtkContainer的destroy函数:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
staticvoid
gtk_container_destroy (GtkObject *object)
{
  GtkContainer *container = GTK_CONTAINER (object);
 
  if(GTK_CONTAINER_RESIZE_PENDING (container))
    _gtk_container_dequeue_resize_handler (container);
 
  if(container->focus_child)
    {
      g_object_unref (container->focus_child);
      container->focus_child = NULL;
    }
 
  /* do this before walking child widgets, to avoid
   * removing children from focus chain one by one.
   */
  if(container->has_focus_chain)
    gtk_container_unset_focus_chain (container);
 
  gtk_container_foreach (container, (GtkCallback) gtk_widget_destroy, NULL);
 
  GTK_OBJECT_CLASS (parent_class)->destroy (object);
}

         事实上,这段中真正负责销毁控件的,便是这句:

?
1
gtk_container_foreach (container, (GtkCallback) gtk_widget_destroy, NULL);

         而gtk_container_foreach代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void
gtk_container_foreach (GtkContainer *container,
               GtkCallback   callback,
               gpointer      callback_data)
{
  GtkContainerClass *class;
   
  g_return_if_fail (GTK_IS_CONTAINER (container));
  g_return_if_fail (callback != NULL);
 
  class= GTK_CONTAINER_GET_CLASS (container);
 
  if(class->forall)
    class->forall (container, FALSE, callback, callback_data);
}

     从这里我们可以看出,gtk_container_destroy将对所有子控件调用gtk_widget_destroy使得它们的ref_count都降1,如果它们的ref_count都等于1的话,显然这些子控件将被销毁——由此可见,用gtk_widget_destroy销毁一个控件及其子控件是有条件的——他们的ref_count都必须等于1,而这一般都是可以满足的——如果你不单独对某个控件使用g_object_ref的话。更重要的是,forall的代码也明确的指出,子控件指的是加入到box中的children链表中的控件(即用gtk_box_pack_start等函数加入到box中的控件)。注意:这里的forall实质上是一个纯虚函数指针,因为GtkContainer控件中的forall指针为NULL,而该指针到box控件中将会被重载:

?
1
container_class->forall = gtk_box_forall;

       而gtk_box_forall的代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
staticvoid
gtk_box_forall (GtkContainer *container,
        gboolean      include_internals,
        GtkCallback   callback,
        gpointer      callback_data)
{
  GtkBox *box = GTK_BOX (container);
  GtkBoxChild *child;
  GList *children;
 
  children = box->children;
  while(children)
    {
      child = children->data;
      children = children->next;
 
      if(child->pack == GTK_PACK_START)
    (* callback) (child->widget, callback_data);
    }
 
  children = g_list_last (box->children);
  while(children)
    {
      child = children->data;
      children = children->prev;
 
      if(child->pack == GTK_PACK_END)
    (* callback) (child->widget, callback_data);
    }
}

       那为什么对GtkAboutDialog控件about调用gtk_widget_destroy并不能销毁控件logo呢?答案已经很显然了,logo并不是GtkAboutDialog的子控件!所以,你如果对GtkAboutDialog控件about调用gtk_widget_destroy的话,并不能销毁logo控件!换句话说,logo控件仍然会占用相应的内存资源,而这正是内存泄漏出现的关键!

       那为什么logo控件不是GtkAboutDialog的子控件呢?

       这是因为把logo控件“加入”到窗体,实质是把它转成GtkImage控件logo_image,并调用gtk_box_pack_start把logo_image加入到GtkAboutDialog控件的窗体中实现的。相关代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void
gtk_about_dialog_set_logo (GtkAboutDialog *about,
                           GdkPixbuf      *logo)
{
  GtkAboutDialogPrivate *priv;
 
  g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
 
  priv = (GtkAboutDialogPrivate *)about->private_data;
 
  g_object_freeze_notify (G_OBJECT (about));
 
  if(gtk_image_get_storage_type (GTK_IMAGE (priv->logo_image)) == GTK_IMAGE_ICON_NAME)
    g_object_notify (G_OBJECT (about),"logo-icon-name");
 
  if(logo != NULL)
    gtk_image_set_from_pixbuf (GTK_IMAGE (priv->logo_image), logo);
  else
    {
      GList *pixbufs = gtk_window_get_default_icon_list ();
 
      if(pixbufs != NULL)
        {
          GtkIconSet *icon_set = icon_set_new_from_pixbufs (pixbufs);
 
          gtk_image_set_from_icon_set (GTK_IMAGE (priv->logo_image),
                                       icon_set, GTK_ICON_SIZE_DIALOG);
 
          gtk_icon_set_unref (icon_set);
          g_list_free (pixbufs);
        }
    }
 
  g_object_notify (G_OBJECT (about),"logo");
 
  g_object_thaw_notify (G_OBJECT (about));
}

         把logo_image加入到GtkAboutDialog控件中的相关代码如下:

?
1
2
priv->logo_image = gtk_image_new ();
gtk_box_pack_start (GTK_BOX (vbox), priv->logo_image, FALSE, FALSE, 0);

         从中也可以看出,logo_name才是GtkAboutDialog的子控件。因此,对GtkAboutDialog控件调用gtk_widget_destroy,是无法销毁gtk_about_dialog_set_logo中logo指针所指向的GdkPixbuf控件的。而在我们写的示例程序中,每次执行show_about_dialog函数,都会执行:

?
1
logo=gdk_pixbuf_new_from_file("./test.png",NULL);

         而在此之前,logo指针所指向的资源并没有被释放,于是内存泄漏就产生了。

         为了解决这一问题,我们需要对logo控件手动调用g_object_unref,故修改后的代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdlib.h>
#include <stdio.h>
#include <gtk/gtk.h>
 
void show_about_dialog(GtkWidget *button);
 
int main(int argc,char**argv)
{
    GtkWidget *window;
    GtkWidget *button;
     
    gtk_init(&argc,&argv);
 
    window=gtk_window_new(GTK_WINDOW_TOPLEVEL);
     
    button=gtk_button_new_with_label("Click me!");
    gtk_container_add(GTK_CONTAINER(window),button);
     
    g_signal_connect(G_OBJECT(button),"clicked",G_CALLBACK(show_about_dialog),NULL);
    g_signal_connect(G_OBJECT(window),"destroy",G_CALLBACK(gtk_main_quit),NULL);
 
    gtk_widget_show_all(window);
     
    gtk_main();
 
    return0;
}
 
voidshow_about_dialog(GtkWidget *button)
{
    GtkWidget *about_dialog;
    GdkPixbuf *logo;
     
    about_dialog=gtk_about_dialog_new();
    logo=gdk_pixbuf_new_from_file("./test.png",NULL);
    gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(about_dialog),logo);
     
    gtk_dialog_run(GTK_DIALOG(about_dialog));
 
    gtk_widget_destroy(about_dialog);
    g_object_unref(G_OBJECT(logo));
 
    return;
 
}

 

Feb 20

在GObject中使用接口

GTK+学习笔记0 Comments

    在GObject中,接口类型(Interface)是一种可以类化(classed)但不可以实例化(instantiable)的类型。所有接口类型都是G_TYPE_INTERFACE的子类,并且不能再被继承,但可以拥有一个乃至多个先决条件。事实上,接口类型都只有类结构类型,而没有实例结构类型。

    例如,GTK_TYPE_EDITABLE就是一个典型的接口类型:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
typedefstruct _GtkEditable       GtkEditable;        /* Dummy typedef */
typedefstruct _GtkEditableClass  GtkEditableClass;
 
struct_GtkEditableClass
{
  GTypeInterface           base_iface;
 
  /* signals */
  void(* insert_text)              (GtkEditable    *editable,
                     constgchar    *text,
                     gint            length,
                     gint           *position);
  void(* delete_text)              (GtkEditable    *editable,
                     gint            start_pos,
                     gint            end_pos);
  void(* changed)                  (GtkEditable    *editable);
 
  /* vtable */
  void(* do_insert_text)           (GtkEditable    *editable,
                     constgchar    *text,
                     gint            length,
                     gint           *position);
  void(* do_delete_text)           (GtkEditable    *editable,
                     gint            start_pos,
                     gint            end_pos);
 
  gchar* (* get_chars)              (GtkEditable    *editable,
                     gint            start_pos,
                     gint            end_pos);
  void(* set_selection_bounds)     (GtkEditable    *editable,
                     gint            start_pos,
                     gint            end_pos);
  gboolean (* get_selection_bounds) (GtkEditable    *editable,
                     gint           *start_pos,
                     gint           *end_pos);
  void(* set_position)             (GtkEditable    *editable,
                     gint            position);
  gint (* get_position)             (GtkEditable    *editable);
};

    从表面上来看,GTK_TYPE_EDITABLE类拥有一个实例结构类型GtkEditable。然而,正如注释所说,这一行代码是所谓的"Dummy typedef",GtkEditable类型其实并不存在(不过我们依然可以定义指向GtkEditable类型的指针变量,也可以将其它的指针类型转化成指向GtkEditable类型的指针类型)。

    为方便叙述,我们假设我们需要定义一个接口类型TYPE_HUMAN,该接口的先决条件是实现了接口TYPE_ANIMAL,而对象TYPE_BOY实现了该接口。

    完整的代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#include <glib-object.h>
 
#define TYPE_ANIMAL (animal_get_type())
#define ANIMAL(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj,TYPE_ANIMAL,Animal))
#define ANIMAL_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE(obj,TYPE_ANIMAL,AnimalInterface))
 
typedefstruct _Animal Animal;
typedefstruct _AnimalInterface AnimalInterface;
 
 
struct_AnimalInterface
{
    GTypeInterface base_iface;
 
    void(*sleep)(Animal *animal);
    void(*eat)(Animal *animal);
    void(*run)(Animal *animal);
};
 
voidanimal_sleep(Animal *animal);
voidanimal_eat(Animal *animal);
voidanimal_run(Animal *animal);
 
 
staticvoid animal_default_init(AnimalInterface *iface);
 
G_DEFINE_INTERFACE(Animal,animal,G_TYPE_INVALID);
 
staticvoid animal_default_init(AnimalInterface *iface)
{
 
}
 
voidanimal_sleep(Animal *animal)
{
    ANIMAL_GET_INTERFACE(animal)->sleep(animal);
}
 
voidanimal_eat(Animal *animal)
{
    ANIMAL_GET_INTERFACE(animal)->eat(animal);
}
 
voidanimal_run(Animal *animal)
{
    ANIMAL_GET_INTERFACE(animal)->run(animal);
}
 
/**********我是分割线**********/
 
#define TYPE_HUMAN (human_get_type())
#define HUMAN(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj,TYPE_HUMAN,Human))
#define HUMAN_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE(obj,TYPE_HUMAN,HumanInterface))
 
typedefstruct _Human Human;
typedefstruct _HumanInterface HumanInterface;
 
struct_HumanInterface
{
    GTypeInterface base_iface;
 
    void(*say)(Human *human);
    void(*study)(Human *human);
};
 
voidhuman_say(Human *human);
voidhuman_study(Human *human);
 
 
staticvoid human_default_init(HumanInterface *iface);
 
G_DEFINE_INTERFACE(Human,human,TYPE_ANIMAL);
 
staticvoid human_default_init(HumanInterface *iface)
{
    staticgboolean is_initialized=FALSE;
     
    if(!is_initialized)
    {
        g_object_interface_install_property(iface,g_param_spec_uint("age","Age","The age of the person",
                                           0,150,0,G_PARAM_READABLE|G_PARAM_WRITABLE));
 
        is_initialized=TRUE;
    }
}
 
voidhuman_say(Human *human)
{
    HUMAN_GET_INTERFACE(human)->say(human);
}
voidhuman_study(Human *human)
{
    HUMAN_GET_INTERFACE(human)->study(human);
}
 
 
/**********我是分割线**********/
 
#define TYPE_BOY (boy_get_type())
#define BOY(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj,TYPE_BOY,Boy))
 
typedefstruct _Boy Boy;
typedefstruct _BoyClass BoyClass;
 
struct_Boy
{
    GObject parent;
 
    GString *name;
    guint age;
};
 
struct_BoyClass
{
    GObjectClass parent_class;
 
};
 
GObject *boy_new_with_name_and_age(gchar *name,guint age);
 
 
enum
{
    PROP_0,
    PROP_AGE
};
 
staticvoid boy_init(Boy *self);
staticvoid boy_class_init(BoyClass *klass);
staticvoid boy_animal_init(AnimalInterface *iface);
staticvoid boy_human_init(HumanInterface *iface);
 
staticvoid boy_finalize(GObject *object);
 
staticvoid boy_get_property(GObject *object,guint property_id,GValue *value,GParamSpec *pspec);
staticvoid boy_set_property(GObject *object,guint property_id,constGValue *value,GParamSpec *pspec);
 
staticvoid boy_sleep(Animal *animal);
staticvoid boy_eat(Animal *animal);
staticvoid boy_run(Animal *animal);
 
staticvoid boy_say(Human *human);
staticvoid boy_study(Human *human);
 
G_DEFINE_TYPE_EXTENDED(Boy,boy,G_TYPE_OBJECT,0,G_IMPLEMENT_INTERFACE(TYPE_ANIMAL,boy_animal_init)\
                       G_IMPLEMENT_INTERFACE(TYPE_HUMAN,boy_human_init));
 
staticvoid boy_init(Boy *self)
{
    self->name=NULL;
}
 
staticvoid boy_class_init(BoyClass *klass)
{
    GObjectClass *oclass;
 
    oclass=G_OBJECT_CLASS(klass);
    oclass->finalize=boy_finalize;
 
    oclass->set_property=boy_set_property;
    oclass->get_property=boy_get_property;
 
    g_object_class_override_property(oclass,PROP_AGE,"age");
}
 
staticvoid boy_animal_init(AnimalInterface *iface)
{
    iface->sleep=boy_sleep;
    iface->eat=boy_eat;
    iface->run=boy_run;
}
 
staticvoid boy_human_init(HumanInterface *iface)
{
    iface->say=boy_say;
    iface->study=boy_study;
}
 
staticvoid boy_finalize(GObject *object)
{
    Boy *boy=BOY(object);
 
    g_string_free(boy->name,TRUE);
 
    G_OBJECT_CLASS(g_type_class_peek_parent(object))->finalize;
}
 
staticvoid boy_get_property(GObject *object,guint property_id,GValue *value,GParamSpec *pspec)
{
    Boy *boy=BOY(object);
 
    switch(property_id)
    {
        case PROP_AGE:
            g_value_set_uint(value,boy->age);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object,property_id,pspec);
 
    }
}
 
staticvoid boy_set_property(GObject *object,guint property_id,constGValue *value,GParamSpec *pspec)
{
    Boy *boy=BOY(object);
 
    switch(property_id)
    {
        case PROP_AGE:
            boy->age=g_value_get_uint(value);
            break;
        default:
            G_OBJECT_WARN_INVALID_PROPERTY_ID(object,property_id,pspec);
 
    }
}
 
staticvoid boy_sleep(Animal *animal)
{
    g_print("I'm sleeping now!\n");
}
 
staticvoid boy_eat(Animal *animal)
{
    g_print("I hate eating vegetables!\n");
}
 
staticvoid boy_run(Animal *animal)
{
    g_print("I will come first!\n");
}
 
staticvoid boy_say(Human *human)
{
    g_print("Hello!\n");
}
 
staticvoid boy_study(Human *human)
{
    g_print("I like Mathematics!\n");
}
 
GObject *boy_new_with_name_and_age(gchar *name,guint age)
{
    GObject *object;
 
    object=g_object_new(TYPE_BOY,"age",age,NULL);
 
    BOY(object)->name=g_string_new(name);
 
 
    returnobject;
}
/**********我是分割线**********/
 
intmain()
{
    GObject *boy;
 
    g_type_init();
 
    boy=boy_new_with_name_and_age("Jim",14);
 
    animal_eat(ANIMAL(boy));
     
    human_study(HUMAN(boy));
 
    return0;
}

    从以上代码可以看出,定义一个接口类型,主要分为以下几步:

    1.定义接口相应的C语言结构:

?
1
2
3
4
5
6
7
8
9
10
typedefstruct _Human Human;
typedefstruct _HumanInterface HumanInterface;
 
struct_HumanInterface
{
    GTypeInterface base_iface;
 
    void(*say)(Human *human);
    void(*study)(Human *human);
};

       所有接口类型的C语言结构,第一个成员必须是GTypeInterface,下面跟的函数指针,则是该接口的虚方法。注意,这里的"typedef struct _Human Human"就是所谓的"Dummy typedef",它使得我们可以为虚方法的形参中那个指向实现该接口的类的某一实例结构类型变量的指针(按惯例应为第一个形参,相当于C++中的this指针)提供一个统一的指针类型。

    2.接下来,我们需要为该接口类型提供一个默认初始化函数(与下面的初始化函数相区别),该函数将负责在运行时刻在GType类型系统中添加该接口类型的信号与属性信息。由于任何一个类在实现该接口时,该默认初始化函数都会被调用,所以我们需要用一个变量来确保负责添加信号与属性的函数体只被调用一次:

?
1
2
3
4
5
6
7
8
9
10
11
12
staticvoid human_default_init(HumanInterface *iface)
{
    staticgboolean is_initialized=FALSE;
     
    if(!is_initialized)
    {
        g_object_interface_install_property(iface,g_param_spec_uint("age","Age","The age of the person",
                                           0,150,0,G_PARAM_READABLE|G_PARAM_WRITABLE));
 
        is_initialized=TRUE;
    }
}

    3.然后,我们需要一个在运行时刻负责在GType类型系统中进行注册的函数human_get_type。当然,我们可以自己“手写”(也许用词不是很准确)这个函数。不过为方便起见我们可以使用宏G_DEFINE_INTERFACE,该宏定义如下:

?
1
#define G_DEFINE_INTERFACE(TN, t_n, T_P)    G_DEFINE_INTERFACE_WITH_CODE(TN, t_n, T_P, ;)

      从中可以看出,该宏有三个参数TN,t_n,T_P。TN和t_n都是接口类型的名字,不同的是,TN要求组成该名字的各个单词的首字母大写,其余字母小写,而t_n则要求组成该名字的各个单词的字母都要小写,各个单词之间要下划线"_"分隔开。该宏要求你定义的接口类型的名字为TN##Interface,接口类型的初始化函数为t_n##_default_init("##"是C语言预处理操作符)。T_P则是该接口类型的先决条件(即实现TN##Interface接口的类在实现该接口之前要实现的接口)GType ID。如果你定义的接口的先决条件不止一个,你可以使用G_DEFINE_INTERFACE_WITH_CODE宏以及g_type_interface_add_prerequisite函数。

    4.为方便使用,我们需要提供一些宏以及所谓的helper function:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
#define TYPE_HUMAN (human_get_type())
#define HUMAN(obj) (G_TYPE_CHECK_INSTANCE_CAST(obj,TYPE_HUMAN,Human))
#define HUMAN_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE(obj,TYPE_HUMAN,HumanInterface))
 
 
void human_say(Human *human)
{
    HUMAN_GET_INTERFACE(human)->say(human);
}
voidhuman_study(Human *human)
{
    HUMAN_GET_INTERFACE(human)->study(human);
}

       宏HUMAN会检测指针obj指向的实例结构类型变量所属的类是否实现了TYPE_HUMAN接口,如果没有,会发出警告,并返回NULL,如果有,则会返回一个指向Human类型的新指针(虽然Human类型实际上并不存在)。

    5.接下来,我们需要让一个类实现该接口,也即提供接口中虚方法的实现并提供一个初始化函数对接口结构变量中的函数指针进行赋值:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
staticvoid boy_say(Human *human);
staticvoid boy_study(Human *human);
 
staticvoid boy_human_init(HumanInterface *iface)
{
    iface->say=boy_say;
    iface->study=boy_study;
}
 
staticvoid boy_say(Human *human)
{
    g_print("Hello!\n");
}
 
staticvoid boy_study(Human *human)
{
    g_print("I like Mathematics!\n");
}

      当然,我们还需要在TYPE_BOY类注册时说明该类实现了TYPE_HUMAN接口,为此,我们在这里使用G_IMPLEMENT_INTERFACE宏,该宏需要配合宏G_DEFINE_TYPE_EXTENDED使用:

?
1
2
G_DEFINE_TYPE_EXTENDED(Boy,boy,G_TYPE_OBJECT,0,G_IMPLEMENT_INTERFACE(TYPE_ANIMAL,boy_animal_init)\
                       G_IMPLEMENT_INTERFACE(TYPE_HUMAN,boy_human_init));

      宏G_IMPLEMENT_INTERFACE的定义如下:

?
1
2
3
4
5
6
#define G_IMPLEMENT_INTERFACE(TYPE_IFACE, iface_init)       { \
  constGInterfaceInfo g_implement_interface_info = { \
    (GInterfaceInitFunc) iface_init, NULL, NULL \
  }; \
  g_type_add_interface_static (g_define_type_id, TYPE_IFACE, &g_implement_interface_info); \
}

      该宏有两个参数TYPE_IFACE和iface_init。TYPE_IFACE是被实现的接口类型的GType ID,而iface_init则是该类针对该接口的初始化函数。

Jan 28

使用Boxed Type机制包装C语言结构体

GTK+学习笔记3 Comments

    注:之前曾写过一篇类似文章,结果被我误删了

 

    按照GObject手册的说法,Boxed Type机制是用来包装(wrap)C语言结构体的一种机制。这里所谓的包装,其实就是在GType类型系统中注册该类型,并且该类型将成为GBoxed的子类。之后,这一类型就能够使用和GBoxed类型有关的所有函数了,如g_value_get_boxed等等。

    首先,我们看一下GBoxed在GType类型系统中的“注册信息”:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void
g_boxed_type_init (void)
{
  staticconst GTypeInfo info = {
    0,                         /* class_size */
    NULL,                      /* base_init */
    NULL,                      /* base_destroy */
    NULL,                      /* class_init */
    NULL,                      /* class_destroy */
    NULL,                      /* class_data */
    0,                         /* instance_size */
    0,                         /* n_preallocs */
    NULL,                      /* instance_init */
    NULL,                      /* value_table */
  };
  constGTypeFundamentalInfo finfo = { G_TYPE_FLAG_DERIVABLE, };
  GType type;
 
  /* G_TYPE_BOXED
   */
  type = g_type_register_fundamental (G_TYPE_BOXED, g_intern_static_string ("GBoxed"), &info, &finfo,
                      G_TYPE_FLAG_ABSTRACT | G_TYPE_FLAG_VALUE_ABSTRACT);
  g_assert (type == G_TYPE_BOXED);
}

    从以上信息中,我们可以看出,GBoxed是一个基本类型,不可实例化(instantiable)、不可类化(classed),可以被继承,但不可以被深继承(即继承了GBoxed的子类不能再被继承)。

    要想在GType类型系统中注册一个Boxed Type,我们需要提供一个copy_func和一个free_func,从名字上也可以看出,前者负责该类型的拷贝,后者负责该类型的释放。

    为更方便地说明问题,我们假设我们需要包装以下的StudentInfo类型:

?
1
2
3
4
5
6
7
8
typedefstruct _StudentInfo StudentInfo;
 
struct_StudentInfo
{
    gint number;
    GString *name;
    gdouble grade;
};

    为了包装该类型,我们自然需要提供一个copy_func和一个free_func:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
StudentInfo *student_info_new(gint number,gchar *name,gdouble grade)
{
    StudentInfo *new_std;
 
    new_std=g_new(StudentInfo,1);
 
    new_std->number=number;
    new_std->name=g_string_new(name);
    new_std->grade=grade;
 
    returnnew_std;
}
 
StudentInfo *student_info_copy(constStudentInfo *std_info)
{
    StudentInfo *new_std;
 
    new_std=g_new(StudentInfo,1);
 
    new_std->number=std_info->number;
    new_std->name=g_string_new(std_info->name->str);
    new_std->grade=std_info->grade;
 
    returnnew_std;
}

    下面,我们需要一个student_info_type_init函数来承担运行时刻注册该类型的任务。为方便起见,我们也为该函数配套了一个宏:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
GType student_info_get_type()
{
    staticGType student_info_id=0;
 
    if(student_info_id==0)
    {
        student_info_id=g_boxed_type_register_static("StudentInfo",(GBoxedCopyFunc)student_info_copy,(GBoxedFreeFunc)student_info_free);
    }
 
    returnstudent_info_id;
}
 
 
#define TYPE_STUDENT_INFO (student_info_get_type())

    从上面的代码中可以看出,Boxed Type的注册工作实质上是由g_boxed_type_register_static函数完成的,该函数原型如下:

?
1
2
3
GType  g_boxed_type_register_static (constgchar *name,
                                     GBoxedCopyFunc boxed_copy,
                                     GBoxedFreeFunc boxed_free);

    该函数有三个参数,其中name是Boxed Type的名字,GBoxedCopyFunc和GBoxedFreeFunc则是用typedef定义的函数指针类型:

?
1
2
typedefgpointer (*GBoxedCopyFunc) (gpointer boxed);
typedefvoid (*GBoxedFreeFunc) (gpointer boxed); 

    至此,包装工作就完成了。

    但是上面给出的student_info_get_type()函数有一个问题:它不是线程安全的。而且,每次写这么多也显得很臃肿,为此,我们可以使用一个宏G_DEFINE_BOXED_TYPE:

?
1
#define G_DEFINE_BOXED_TYPE(TypeName, type_name, copy_func, free_func) G_DEFINE_BOXED_TYPE_WITH_CODE (TypeName, type_name, copy_func, free_func, {})

    该宏有4个参数:

  • TypeName: 该参数是该Boxed Type的名字,且不用引号包围。
  • type_name: 该参数将成为_get_type()的前缀,即最终自动生成的函数的名字为type_name_get_type()
  • copy_func: 该参数为函数指针或函数名(函数指针常量?),其类型应为以下三种之一:
    ?
    1
    2
    3
    typedefgpointer (*copy_func) (gpointer boxed);
    typedefTypeName *(*copy_func) (constTypeName *boxed);
    typedefTypeName *(*copy_func) (TypeName *boxed);
  • free_func: 该参数为函数指针或函数名(函数指针常量?),其类型应为以下两种之一:
    ?
    1
    2
    typedefvoid (*free_func) (gpointer boxed);
    typedefvoid (*free_func) (TypeName *boxed);

    因此,如果想生成student_info_get_type()函数的话,只要按如下所示填写参数:

?
1
G_DEFINE_BOXED_TYPE(StudentInfo,student_info,student_info_copy,student_info_free);

    如果你由于种种原因而需要自己写*_get_type()函数的话,为使其线程安全,可以将以下代码作为模板使用:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GType
g_strv_get_type (void)
{
  staticvolatile gsize g_define_type_id__volatile = 0;
 
  if(g_once_init_enter (&g_define_type_id__volatile))
    {
      GType g_define_type_id =
        g_boxed_type_register_static (g_intern_static_string ("GStrv"),
                                      (GBoxedCopyFunc) g_strdupv,
                                      (GBoxedFreeFunc) g_strfreev);
 
      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
    }
 
  returng_define_type_id__volatile;
}

    事实上,很久以前,G_DEFINE_BOXED_TYPE展开以后,也是和上面的代码类似的——之所以要改用现在的写法,主要还是为了增强对类型的检测。

    完整的测试代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <glib.h>
#include <glib-object.h>
#include <stdlib.h>
 
typedefstruct _StudentInfo StudentInfo;
 
struct_StudentInfo
{
    gint number;
    GString *name;
    gdouble grade;
};
 
StudentInfo *student_info_new(gint number,gchar *name,gdouble grade)
{
    StudentInfo *new_std;
 
    new_std=g_new(StudentInfo,1);
 
    new_std->number=number;
    new_std->name=g_string_new(name);
    new_std->grade=grade;
 
    returnnew_std;
}
 
voidstudent_info_print(constStudentInfo *std_info)
{
    g_print("Number:%3d Name:%5s Grade:%3.1f",std_info->number,std_info->name->str,std_info->grade);
}
 
StudentInfo *student_info_copy(constStudentInfo *std_info)
{
    StudentInfo *new_std;
 
    new_std=g_new(StudentInfo,1);
 
    new_std->number=std_info->number;
    new_std->name=g_string_new(std_info->name->str);
    new_std->grade=std_info->grade;
 
    returnnew_std;
}
 
voidstudent_info_free(StudentInfo *std_info)
{
    g_string_free(std_info->name,TRUE);
 
    g_free(std_info);
}
 
G_DEFINE_BOXED_TYPE(StudentInfo,student_info,student_info_copy,student_info_free);
 
#define TYPE_STUDENT_INFO (student_info_get_type())
 
int main()
{
    StudentInfo *stdi;
    StudentInfo *stdi_copy;
 
    g_type_init();
 
    stdi=student_info_new(1,"JIm",86.5);
    stdi_copy=g_boxed_copy(TYPE_STUDENT_INFO,stdi);
 
    student_info_print(stdi_copy);
 
    g_boxed_free(TYPE_STUDENT_INFO,stdi);
    g_boxed_free(TYPE_STUDENT_INFO,stdi_copy);
 
    return0;
}

 

Jan 26

对g_signal_new()参数的解释

GTK+学习笔记4 Comments

    在GObject中,如果想给自己写的类加上signal,一般需要在*_class_init函数中使用g_signal_new函数,但是这个函数的参数比较复杂:

?
1
2
3
4
5
6
7
8
9
10
guint    g_signal_new(constgchar *signal_name,
                      GType itype,
                      GSignalFlags signal_flags,
                      guint class_offset,
                      GSignalAccumulator accumulator,
                      gpointer accu_data,
                      GSignalCMarshaller c_marshaller,
                      GType return_type,
                      guint n_params,
                      ...);

    从该函数的原型中,我们可以看出,该函数的参数个数是可变的。

    下面,我姑妄从前往后依次解释一下各个参数的含义:

  • const gchar *signal_name:该参数是信号的名字,由分隔符以及ASCII码中的字母和数字构成,并且第一个字符必须是字母。分隔符可以是"-"或"_"——事实上,系统会先调用g_strdelimit把"_"转化为"-"再存储signal_name。因此,在调用g_singal_emit_by_name时,detailed_signal参数中的分隔符必须是"-"。
  • GType itype:该参数是signal所依附的类的在GType类型系统中注册时得到的ID,也就是*_get_type()函数的返回值。
  • GSignalFlags signal_flags:该参数是信号的属性标记,有七种,我将在最后解释:
    • G_SIGNAL_RUN_FIRST
    • G_SIGNAL_RUN_LAST
    • G_SIGNAL_RUN_CLEANUP
    • G_SIGNAL_NO_RECURSE
    • G_SIGNAL_DETAILED
    • G_SIGNAL_ACTION
    • G_SIGNAL_NO_HOOKS
  • guint class_offset:该参数是itype对应的类的class结构中的一个函数指针相对于class结构的实例的首地址的偏移量。该函数指针所对应的函数常被称为"per-object handler","default (signal) handler"或"object methond handler",并将在信号发出后被调用(如调用g_signal_emit_by_name)。常配合宏G_STRUCT_OFFSET使用(该宏能够返回结构体变量的成员相对于该结构体的变量的首地址的偏移量)。如果设为0,则表示该类没有"per-object handler"。
  • GSignalAccumulator accumulator:该参数是一个函数指针,其对应的函数将在该信号的每个handler执行完以后执行。GSignalAccumulator类型的定义如下:
    ?
    1
    2
    3
    4
    typedefgboolean (*GSignalAccumulator)  (GSignalInvocationHint *ihint,
                                             GValue *return_accu,
                                             constGValue *handler_return,
                                             gpointer data);
    从中可以看出该函数指针类型有4个参数:
  • GSignalInvocationHint *ihint:该参数是一个GSignalInvocationHint类型的指针,而GSignalInvocation结构的定义如下:
    ?
    1
    2
    3
    4
    5
    typedefstruct {
      guint signal_id;
      GQuark detail;
      GSignalFlags run_type;
    } GSignalInvocationHint;

其中,signal_id是导致GSignalAccumulator所指函数被调用的signal的ID(因为该信号signal调用了某个handler)。detail是该signal的detail部分对应的GQuark(其实就是字符串的散列值)。run_type则反映了handler被调用时signal发射进行到的阶段,其值为 G_SIGNAL_RUN_FIRST,G_SIGNAL_RUN_LAST或G_SIGNAL_CLEANUP。

  • GValue *return_accu:该参数是一个GValue类型的指针,可以被程序员用来返回任何在GType类型系统注册的,带有value_table的类型。
  • const GValue *handler_return:该参数是一个GValue类型的指针,指向一个GValue变量,该变量存储了GSignalAccumulator所指函数被调用前,信号调用的handler的返回值,可以用g_value_get_*系列函数获取其存储的值。
  • gpointer data:该参数即为g_signal_new的参数gpointer accu_data的值

显然,该函数的返回类型为gboolean。如果其返回值为FASLE,则signal发射过程就会被中止(即不再调用后面的hander),否则会继续下去。事实上,"delete-event"等带有event后缀的signal就是利用了这一点——这些信号的某个回调函数如果返回了TRUE,则以后的回调函数就不会被调用。

我们可以看一下"delete-event"的accumulator, _gtk_boolean_handled_accumulator的代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gboolean
_gtk_boolean_handled_accumulator (GSignalInvocationHint *ihint,
                  GValue                *return_accu,
                  constGValue          *handler_return,
                  gpointer               dummy)
{
  gboolean continue_emission;
  gboolean signal_handled;
   
  signal_handled = g_value_get_boolean (handler_return);
  g_value_set_boolean (return_accu, signal_handled);
  continue_emission = !signal_handled;
   
  returncontinue_emission;
}

注意,如果该signal有accumulator,该signal的回调函数类型(由c_marshaller反映)必须有返回值,否则该signal不能有accumulator,也即在调用g_signal_new时以NULL作为该形参的实参。

  • gpointer accu_data:该参数将作为用户自定义参数传入accumulator所指向的函数中。
  • GSignalCMarshaller c_marshaller:该参数是一个GSignalCMarshall类型的函数指针,其值反映了回调函数的返回值类型和额外参数类型(所谓“额外参数”,即指除回调函数中instance和user_data以外的参数)。            

例如,g_closure_marshal_VOID_VOID说明该signal的回调函数为以下的callback类型:

typedef void (*callback)  (gpointer instance, gpointer user_data);

而g_closure_marshal_VOID_POINTER则说明该signal的回调函数为以下的callback类型:

typedef void (*callback)  (gpointer instance, gpointer arg1, gpointer user_data);

如果默认提供的GClosureMarshall中没有你需要的,你可以用glib-genmarshall生成它,具体可参见devhelp中有关glib-genmarshall的说明。

  • GType return_type:该参数的值应为回调函数的返回值在GType类型系统中的ID。
  • guint n_params:该参数的值应为回调函数的额外参数的个数。
  • ...:这一系列的参数的值应为回调函数的额外参数在GType类型系统中的ID,且这一系列参数中第一个参数的值为回调函数的第一个额外参数在GType类型系统中的ID,依次类推。

 

最后,我们来解释一下GSignalFlags中各个特征标志的含义:

  • G_SIGNAL_RUN_FIRST:调用回调函数时,"per-object handler"对应的回调函数将第一个调用
  • G_SIGNAL_RUN_LAST:调用回调函数时,"per-object handler"对应的回调函数将在用户用g_signal_connect连接的回调函数之后调用,并在用户用g_signal_connect_after连接的回调函数之前调用
  • G_SIGNAL_RUN_CLEANUP:调用回调函数时,"per-object handler"对应的回调函数将最后一个调用
  • G_SIGNAL_NO_RECURSE:信号发射时,如果信号的上次发射还没有结束,那么本次信号发射将不再进行,而只是使上次的信号发射重新开始。
  • G_SIGNAL_DETAILED:信号名字可以使用"signal_name::detailed"的形式。
  • G_SIGNAL_ACTION:程序员可以在代码中自由地调用g_signal_emit族的函数来发射信号,而不需要把g_signal_emit族的函数放在一段代码中再来调用。
  • G_SIGNAL_NO_HOOKS:信号发射过程中不支持钩子函数。

 

例子请看cloverprice的文章:《GObject 08: A class with a signal》

传送门:http://cloverprince.javaeye.com/blog/500964

 

注:信号的发射以我的理解,就是按照要求调用回调函数的过程……

原创粉丝点击