信号的定义和应用
在GObject系统中,信号是一种定制对象行为的手段,同时也是一种多种用途的通知机制。初学者可能是在GTK+中首先接触到信号这一概念的,事实上在普通的字符界面编程中也可以正常应用,这可能是很多初学者未曾想到的。
一个对象可以没有信号,也可以有多个信号。当有一或多个信号时,信号的名称定义是必不可少的,此时C语言的枚举类型的功能就凸显出来了,用LAST_SIGNAL来表示最后一个信号(不用实现的信号)是一种非常良好的编程风格。这里为Boy对象定义了一个信号BOY_BORN,在对象创建时发出,表示Boy对象诞生。
同时还需要定义静态的整型指针数组来保存信号的标识,以便于下一步处理信号时使用。
对象的类结构是所有对象的实例所共有的,我们将信号也定义在对象的类结构中,如此信号同样也是所有对象的实例所共有的,任意一个对象的实例都可以处理信号。因此我们有必要在在类初始化函数中创建信号(这也可能是GObject设计者的初衷)。函数g_signal_new用来创建一个新的信号,它的详细使用方法可以在GObject的API文档中找到。信号创建成功后,返回一个信号的标识ID,如此就可以用发射信号函数g_signal_emit向指定义对象的实例发射信号,从而执行相应的功能。
本例中每创建一个新的Boy对象,就会发射一次BOY_BORN信号,也就会执行一次我们定义的boy_born函数,也就输出一行"Message : A boy was born ."信息。
对象的属性和方法
对象实例所有的属性和方法一般都定义在对象的实例结构中,属性定义为变量或变量指针,而方法则定义为函数指针,如此,我们一定要定义函数为static类型,当为函数指针赋值时,才能有效。
对象的继承
以下为继承自Boy对象的Man对象的实现,Man对象在Boy对象的基础上又增加了一个属性job和一个方法bye。
#ifndef __MAN_H__ #define __MAN_H__
#include "boy.h"
#define MAN_TYPE ( man_get_type() ) #define MAN(obj) ( G_TYPE_CHECK_INSTANCE_CAST ( (obj), MAN_TYPE, Man ) )
typedef struct _Man Man; typedef struct _ManClass ManClass;
struct _Man { Boy parent; char *job; void (*bye)(void); };
struct _ManClass { BoyClass parent_class; };
GType man_get_type ( void );
Man* man_new ( void );
gchar* man_get_job ( Man *man );
void man_set_job ( Man *man, gchar *job );
Man* man_new_with_name_age_and_job ( gchar *name, gint age, gchar *job );
void man_info ( Man *man );
#endif //__MAN_H__
/* man.c */
#include "man.h"
static void man_bye ( void ); static void man_init ( Man *man ); static void man_class_init ( Man *man );
GType man_get_type ( void ) { static GType man_type = 0; if ( !man_type ) { static const GTypeInfo man_info = { sizeof( ManClass ), NULL, NULL, (GClassInitFunc)man_class_init, NULL, NULL, sizeof( Man ), 0, (GInstanceInitFunc) man_init }; man_type = g_type_register_static ( BOY_TYPE, "Man", &man_info, 0); } return man_type; }
static void man_init ( Man *man ) { man->job = "none"; man->bye = man_bye; }
static void man_class_init ( Man *man ) {
}
Man* man_new ( void ) { Man *man; man = g_object_new ( MAN_TYPE, 0 ); return man; }
gchar* man_get_gob ( Man *man ) { return man->job; }
void man_set_job ( Man *man, gchar *job ) { man->job = job; }
Man* man_new_with_name_age_and_job ( gchar *name, gint age, gchar *job ) { Man *man; man = man_new ( ); boy_set_name ( BOY (man), name ); boy_set_age ( BOY(man), age ); man_set_job ( man, job ); return man; } static void man_bye(void) { g_print ( "Goodbye everyone !/n" ); } void man_info(Man *man) { g_print ( "the man name is %s/n", BOY(man)->name ); g_print ( "the man age is %d/n", BOY(man)->age ); g_print ( "the man job is %s/n", man->job ); }
关键在于定义对象时将父对象实例定义为Boy,父类设定为BoyClass,在注册此对象时将其父对象类型设为BOY_TYPE,
在设定对象属性时如用到父对象的属性要强制转换下,如取得对象的name属性,就必须用BOY(obj)->name,因为Man本身没有name属性,
而其父对象Boy有,所以用BOY宏将其强制为Boy类型的对象。
测试我们定义的对象
#include <glib.h> #include "boy.h" #include "man.h"
int main ( int argc, char *argv[] ) { Boy *tom, *peter;
Man *green, *brown;
g_type_init();//注意,初始化类型系统,必需
tom = boy_new_with_name("Tom");
tom->cry();
boy_info(tom);
peter = boy_new_with_name_and_age("Peter", 10);
peter->cry();
boy_info(peter);
green = man_new();
boy_set_name(BOY(green), "Green");
//设定Man对象的name属性用到其父对象Boy的方法 boy_set_age(BOY(green), 28);
man_set_job(green, "Doctor");
green->bye();
man_info(green);
brown = man_new_with_name_age_and_job( "Brown", 30, "Teacher" );
brown->bye();
man_info(brown);
}
Makefile文件如下: CC = gcc all: $(CC) -c boy.c `pkg-config --cflags glib-2.0 gobject-2.0` $(CC) -c man.c `pkg-config --cflags glib-2.0 gobject-2.0` $(CC) -c main.c `pkg-config --cflags glib-2.0 gobject-2.0` $(CC) -o simple boy.o man.o main.o `pkg-config --libs glib-2.0 gobject-2.0`
执行make命令编译,编译结束后,执行./simple运行此测试程序,输出结果如下:
Message : A boy was born .
The Boy is crying ......
The Boy name is Tom
The Boy age is 0
Message : A boy was born .
The Boy is crying ......
The Boy name is Peter
The Boy age is 10
Goodbye everyone !
the man name is Green
the man age is 28
the man job is Doctor
Goodbye everyone !
the man name is Brown
the man age is 30
the man job is Teacher
Makefile中用到`pkg-config -cflags -libs gobject-2.0`,在GLIB中将线程(gthread),插件(gmoudle)和对象系统(gobject)
这三个子系统区别对待,编译时要注意加入相应的参数。
本文只是概要的介绍了如何定义和实现GObject对象,GObject系统中还有很多相关内容,
如:枚举和标识类型(Enumeration and flags types);Gboxed,是Gtype系统中注册一种封装为不透明的C语言结构类型的机制;
许多对象用到的参数对象都是C结构类型,使用者不必了解其结构的内部定义,即不透明,GBoxed即是实现这一功能的机制;
标准的参数和变量类型的定义(Standard Parameter and Value Types)等,它们都以C语言来开发,是深入了解和掌握GObject的关键。
透过以上代码实现,我们还可以看出,以GLIB为基础的GTK+/GNOME开发环境所具有的独特的编程风格和独到的开发思想。
这一点在长期的编程实践中会体验得更深刻。
有了GObject系统这一基础,GTK+通过它将X窗口环境中的控件(Widget)巧妙的封装起来,这使开发LINUX平台上的GUI应用程序
更方便,更快捷。
以上代码在Redhat 8.0 Linux平台,GLIB2.2.1环境下编译通过。
感谢
本文的写作参考了中国LINUX论坛的网友hoyt的文章,可以在gtkvb.cffd.org.cn找到,在此表示感谢。
参考资料
- 本文的所有代码可以在这里 下载。
- Thomas Hunger写的文章: Gobject tutorial
- 中国 linux 论坛的网友 hoyt 的网页 http://gtkvb.cosoft.org.cn/上的文章
- 与本文相关的另一篇文章 《浅析GLib》
关于作者
| 宋国伟,乡村小学英语教师,他是《GTK+2.0编程范例》(清华大学出版社出版)一书的作者,业余时间致力于用GTK+开发LINUX GUI应用程序,可以通过电子邮件地址 gwsong52@sohu.com与他联系。 |