GLib是GTK+和GNOME工程的基础底层核心程序库,是一个综合用途的实用的轻量级的C程序库,它提供C语言的常用的数据结构的定义、相关的处理函数,有趣而实用的宏,可移植的封装和一些运行时机能,如事件循环、线程、动态调用、对象系统等的API。它能够在类UNIX的操作系统平台(如LINUX, HP-UNIX等),WINDOWS,OS2和BeOS等操作系统台上运行。
GLib需要一个支持线程的操作系统和一个字符集间转换函数iconv的支持,事实上大多现代的操作系统都有以上两项功能。
GLib由基础类型、对核心应用的支持、实用功能、数据类型和对象系统五个部分组成的。
GLib的最新版本是GLib2.2.1,可以到www.gtk.org网站下载其源代码。使用GLib2.0编写的应用程序,在编译时应该在编译命令中加入`pkg-config -cflags -libs glib-2.0`,如编译一个名为hello.c的程序,输出名为hello的可执行文件,则命令为:
gcc `pkg-config -cflags -libs glib-2.0` hello.c -o hello
在GLIB中将线程(gthread),插件(gmoudle)和对象系统(gobject)这三个子系统区别对待,编译时要注意加入相应的参数。
如程序中用到对象系统,编译时就应加入:
`pkg-config --cflags --libs gobject-2.0`
用到线程,编译时则加入:
`pkg-config --cflags --libs gthread-2.0`
用到插件,编译时则加入:
`pkg-config --cflags --libs gmoudle-2.0`
基础类型
GLib的基础是由基础类型、范围限定宏、标准宏、类型转换宏、字节次序变换宏、数学常数定义和杂项宏等各项组成的。这里主要介绍基础类型,因为它们遍及与GLIB相关的各种程序库和软件包中,如GTK+,GNOME,MONO等大的开源项目。
基础类型又称标准类型,GLib将C语言中的数据类型统一封装成自己的数据类型,均以小写字母'g'开头,如:gpointer是指针类型(void *)、guint是无符号整型(unsigned int)等,其中有一些是修饰性的,如:gint、gchar等,它们和C语言中的int、char是完全相同的。这些数据类型使用起来和C语言中的数据类型完全一样,当你熟悉了以后会发现它们的使用方法更灵活,更直观也更易于理解一些。当然你可以把C语言中的数据类型直接拿来使用,这丝毫不影响你编写程序的编译。
另外范围限定宏和类型转换宏也较常用,如G_MAXINT表示最大的int型值,用宏GINT_TO_POINTER(i)将整型变量i转换为指针型,宏GPOINTER_TO_INT(p)将指针型变量p转换为整型。
逻辑类型gboolean的值TRUE和FALSE是在常数宏中定义的,另外还包括G_E表示自然对数,G_PI表示圆周率,G_PI_2表示圆周率的1/2等一些数学常数。
对核心应用的支持
GLib对核心应用的支持包括事件循环、内存操作、线程操作、动态链接库的操作和出错处理与日志等。
下面代码演示了事件循环、内存操作、线程这三种功能的简单应用:
#include <glib.h> static GMutex *mutex = NULL; static gboolean t1_end = FALSE; static gboolean t2_end = FALSE; typedef struct _Arg Arg; struct _Arg { GMainLoop* loop; gint max; }; void run_1(Arg *arg) { int i ; for(i=0; i<arg->max; i++) { if(g_mutex_trylock(mutex) == FALSE) { //g_print("%d : thread 2 locked the mutex \n", i); g_print("%d :线程2锁定了互斥对象\n", i); g_mutex_unlock(mutex); } else { g_usleep(10); } } t1_end = TRUE; } void run_2(Arg *arg) { int i; for(i=0; i<arg->max; i++) { if(g_mutex_trylock(mutex) == FALSE) { //g_print("%d : thread 1 locked mutex \n", i); g_print("%d :线程1锁定了互斥对象\n", i); g_mutex_unlock(mutex); } else { g_usleep(10); } } t2_end = TRUE; } void run_3(Arg *arg) { for(;;) { if(t1_end && t2_end) { g_main_loop_quit(arg->loop); break; } } } int main(int argc, char *argv[]) { GMainLoop *mloop; Arg *arg; if(!g_thread_supported()) g_thread_init(NULL); mloop = g_main_loop_new(NULL, FALSE); arg = g_new(Arg, 1); arg->loop = mloop; arg->max = 11; mutex = g_mutex_new(); g_thread_create(run_1, arg, TRUE, NULL); g_thread_create(run_2, arg, TRUE, NULL); g_thread_create(run_3, arg, TRUE, NULL); g_main_loop_run(mloop); g_print("线程3退出事件循环\n"); g_mutex_free(mutex); g_print("释放互斥对象\n"); g_free(arg); g_print("释放参数所用的内存\n"); }
Makefile文件如下:
CC = gcc all: $(CC) `pkg-config --cflags --libs glib-2.0 gthread-2.0` loop.c -o loop
下面为输出结果:
0 :线程1锁定了互斥对象
1 :线程2锁定了互斥对象
2 :线程1锁定了互斥对象
3 :线程2锁定了互斥对象
4 :线程1锁定了互斥对象
5 :线程2锁定了互斥对象
6 :线程1锁定了互斥对象
7 :线程2锁定了互斥对象
8 :线程1锁定了互斥对象
9 :线程2锁定了互斥对象
10 :线程1锁定了互斥对象
线程3退出事件循环
释放互斥对象
释放参数所用的内存
以上例程创建了三个线程,其中run_1和run_2操作互斥对象,run_3检索前两个线程是否结束,如结束的话,则执行g_main_loop_quit退出事件循环。由于线程的运行是不确定的,所以不一定每次都是这一输出结果。
首先定义一个结构类型来保存创建的事件循环的对象指针和线程运行时的最多循环次数,一般情况下,如果为此数据结构来分配内存的话,用Arg *arg = (Arg *)malloc(sizeof(Arg));,释放时用free(arg);,这种传统的做法曾经让很多C语言的初学者头痛,尤其是需要多次操作的时候,GLib中提供了类似的函数g_malloc和g_free,最好用的方法是其将g_malloc函数封装成了宏g_new,这个宏有两个参数,第一个是结构类型,第二个是要分配结构的数量,这段代码中只用到了一个Arg数据结构,所以是g_new(Arg, 1)。在程序结束时用g_free来释放。
在线程初始化时,首先是判断线程是否初始化的函数g_thread_supported,如果其返回FALSE则表明线程并未初始化,这时必须用g_thread_init来初始化,这是较明智的做法。
事件循环GMainLoop在用g_main_loop_new创建之后并不马上运行,用g_main_loop_run运行后,还要用g_main_loop_quit退出,否则循环将一直运行下去,这两个函数的参数都是GMainLoop型的指针,在主函数中并未直接运行g_main_loop_quit,而是把它放在线程的函数中了,这一点需读者注意。
实用功能
GLib中包含了近二十种实用功能,从简单的字符处理到初学者很难理解的XML解析功能,这里介绍两种较简单的:随机数和计时。
下面代码演示如何产生1-100之间的随机整数和演示如何计算30000000次累加在计算时用的时间:
/* until.c 用来测试实用功能 */ #include <glib.h> int main(int argc, char *argv[]) { GRand *rand; GTimer *timer; gint n; gint i, j; gint x = 0; rand = g_rand_new(); //创建随机数对象 for(n=0; n<20; n++) { //产生随机数并显示出来 g_print("%d\t",g_rand_int_range(rand,1,100)); } g_print("\n"); g_rand_free(rand); //释放随机数对象 //创建计时器 timer = g_timer_new(); g_timer_start(timer);//开始计时 for(i=0; i<10000; i++) for(j=0; j<3000; j++) x++;//累计 g_timer_stop(timer);//计时结束 //输出计时结果 g_print("%ld\tall:%.2f seconds was used!\n",x,g_timer_elapsed(timer,NULL)); }
Makefile文件内容如下:
CC = gccall:$(CC) `pkg-config --cflags --libs glib-2.0 ` until.c -o until
输出结果:
48 95 95 99 90 24 90 29 78 4 53 87 1 86 7 93 57 88 75 4 30000000 all:1.47 seconds was used!
GLIB中的每个对象几乎都有一个或多个*_new函数来创建,计时器GTimer和随机器GRand也一样,也都有相对应的函数来结束对象的使用,如GTimer的g_timer_stop和GRand的g_rand_free。
这可能是GLIB实用功能中最简单的两种了,许多朋友会一目了然。我们还应注意到GLIB的代码风格和封装技巧是具有独到之处的,这种风格和技巧足以让一些自称简洁实用的SDK汗颜,学习掌握这一风格可能会让我们受益匪浅。
数据类型
GLib中定义了十几种常用的数据结构类型和它们的相关操作函数,下面是关于字符串类型的简单示例:
#include <glib.h> int main(int argc, char *argv[]) { GString *s; s = g_string_new("Hello"); g_print("%s\n", s->str); s = g_string_append(s," World!"); g_print("%s\n",s->str); s = g_string_erase(s,0,6); g_print("%s\n",s->str); s = g_string_prepend(s,"Also a "); g_print("%s\n",s->str); s = g_string_insert(s,6," Nice"); g_print("%s\n",s->str); }
Makefile文件如下:
CC = gcc all: $(CC) `pkg-config --cflags --libs glib-2.0 ` string.c -o str
下面是输出结果:
Hello Hello World! World! Also a World! Also a Nice World!
字符串在编程中出现频率之高,即使是初学者也很清楚,追加、删除和插入等常用操作理解后,还可以进一步了解掌握其它更复杂的操作。
GLib提供了一种内存块(GMemChunk)数据类型,它为分配等大的内存区提供了一种非常好用的操作方式,下面程序演示了内存块数据类型的简单用法:
#include <glib.h> int main(int argc, char *argv[]) { GMemChunk *chunk; //定义内存块 gchar *mem[10]; //定义指向原子的指针数组 gint i, j; //创建内存块 chunk = g_mem_chunk_new("Test MemChunk", 5, 50, G_ALLOC_AND_FREE); //名称,原子的长度, 内存块的长度,类型 for(i=0; i<10; i++) { //创建对象 //mem[i] = g_chunk_new(gchar, chunk); mem[i] = (gchar*)g_mem_chunk_alloc(chunk); for(j=0; j<5; j++) { mem[i][j] = 'A' + j;//为内存块中的指针赋值 } } g_mem_chunk_print(chunk); //显示内存块信息 for(i=0; i<10; i++) { g_print("%s\t",mem[i]);//显示内存块中的内容 } for(i=0; i<10; i++) { g_mem_chunk_free(chunk,mem[i]); //释放所有分配的内存 } g_mem_chunk_destroy(chunk); }
Makefile文件件如下:
CC = gcc all: $(CC) `pkg-config --cflags --libs glib-2.0` data1.c -o data1
以下为输出结果:
GLib-INFO: Test MemChunk: 80 bytes using 2 mem areas ABCDE ABCDE ABCDE ABCDE ABCDE ABCDE ABCDE ABCDE ABCDE ABCDE
这里说明这一数据类型的原因是通过它可以他细体会内存分配这一运行时处理环节的应用之妙。
我们在程序中分配的是50字节的空间,而实际用的是80字节,由此可以看出其在分配内存时本身用到了部分内存空间。
从上面的示例代码中可以看出,在GLib中几乎所有的对象都是C语言的结构类型,一般命名以大写字母G开头的单词,如GList表示双向链表,所有与之相关的操作函数都以小写的字母g加下划线加小写的单词加下划线开头,如以g_list_*开头的函数都是与这相关的操作函数,而且这些函数中的第一个参数多数是此对象的指针。
GLIB中的数据类型在GLIB本身,尤其是GTK+中频繁用到,了解掌握这些数据类据类型的用法是非常必要的,这对进一步灵活开发GTK+程序来说是关键一环,而且是对大学中的《数据结构》一科的很好回顾。
GObject对象系统
简单的说,GObject对象系统是一个建立在GLIB基础上的,用C语言完成的,具有跨平台特色的、灵活的、可扩展的、非常容易映射到其它语言的面向对象的框架。如果你是一个C语言的执着的追随者,你没有理由不研究一下它。
前言
大多数现代的计算机语言都带有自己的类型和对象系统,并附带算法结构。正象GLib提供的基本类型和算法结构(如链表、哈希表等)一样,GObject的对象系统提供了一种灵活的、可扩展的、并容易映射(到其它语言)的面向对象的C语言框架。它的实质可以概括为:
- 一个通用类型系统,用来注册任意的、轻便的、单根继承的、并能推导出任意深度的结构类型的界面,它照顾组合对象的定制、初始化和内存管理,类结构,保持对象的父子关系,处理这些类型的动态实现。也就是说,这些类型的实现是在运行时重置和卸载的;
- 一个基本类型的实现集,如整型,枚举型和结构型等;
- 一个基本对象体系之上的基本对象类型的实现的例子--GObject基本类型;
- 一个信号系统,允许用户非常灵活的自定义虚的或重载对象的方法,并且能充当非常有效力的通知机制;
- 一个可扩展的参数/变量体系,支持所有的能被用作处理对象属性或其它参数化类型的基本的类型。
类型(GType)与对象(GObject)
GLib中最有特色的是它的对象系统--GObject System,它是以Gtype为基础而实现的一套单根继承的C语言的面向对象的框架。
GType 是GLib 运行时类型认证和管理系统。GType API 是GObject的基础系统,所以理解GType是理解GObject的关键。Gtype提供了注册和管理所有基本数据类型、用户定义对象和界面类型的技术实现。(注意:在运用任一GType和GObject函数之前必需运行g_type_init()函数来初始化类型系统。)
为实现类型定制和注册这一目的,所有类型必需是静态的或动态的这二者之一。静态的类型永远不能在运行时加载或卸载,而动态的类型则可以。静态类型由g_type_register_static()创建,通过GTypeInfo结构来取得类型的特殊信息。动态类型则由g_type_register_dynamic()创建,用GTypePlugin结构来取代GTypeInfo,并且还包括g_type_plugin_*()系列API。这些注册函数通常只运行一次,目的是取得它们返回的专有类的类型标识。
还可以用g_type_register_fundamental来注册基础类型,它同时需要GTypeInfo和GTypeFundamentalInfo两个结构,事实上大多数情况下这是不必要的,因为系统预先定义的基础类型是优于用户自定义的。
(本文重点介绍创建和使用静态的类型。)
对象的定义
在GObject系统中,对象由三个部分组成:
- 对象的ID标识(唯一,无符号长整型,所有此类对象共同的标识);
- 对象的类结构(唯一,结构型,由对象的所有实例共同拥有);
- 对象的实例(多个,结构型,对象的具体实现)。
基于GObject的对象到底是什么样的呢?下面是基于GObject的简单对象 -- Boy的定义代码:
/* boy.h */ #ifndef __BOY_H__ #define __BOY_H__ #include <glib-object.h> #define BOY_TYPE (boy_get_type()) #define BOY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),BOY_TYPE,Boy)) typedef struct _Boy Boy; typedef struct _BoyClass BoyClass; struct _Boy { GObject parent; // gint age; gchar *name; void (*cry)(void); }; struct _BoyClass { GObjectClass parent_class; // void (*boy_born)(void); }; GType boy_get_type(void); Boy* boy_new(void); int boy_get_age(Boy *boy); void boy_set_age(Boy *boy, int age); char* boy_get_name(Boy *boy); void boy_set_name(Boy *boy, char *name); Boy* boy_new_with_name(gchar *name); Boy* boy_new_with_age(gint age); Boy* boy_new_with_name_and_age(gchar *name, gint age); void boy_info(Boy *boy); #endif /* __BOY_H__*/
这是一段典型的C语言头文件定义,包括编译预处理,宏定义,数据结构定义和函数声明;首先要看的是两个数据结构对象Boy和BoyClass,
结构类型_Boy是Boy对象的实例,就是说我们每创建一个Boy对象,也就同时创建了一个Boy结构。Boy对象中的parent表示此对象的父类,GObject系统中所有对象的共同的根都是GObject类,所以这是必须的;其它的成员可以是公共的,这里包括表示年龄的age,表示名字的name和表示方法的函数指针cry,外部代码可以操作或引用它们。
结构类型_BoyClass是Boy对象的类结构,它是所有Boy对象实例所共有的。BoyClass中的parent_class是GObjectClass,同GObject是所有对象的共有的根一样,GObejctClass是所有对象的类结构的根。在BoyClass中我们还定义了一个函数指针boy_born,也就是说这一函数指针也是所有Boy对象实例共有的,所有的Boy实例都可以调用它;同样,如果需要的话,你也可以在类结构中定义其它数据成员。
其余的函数定义包括三种,一种是取得Boy对象的类型ID的函数boy_get_type,这是必须有的;另一种是创建Boy对象实例的函数boy_new和boy_new_with_*,这是非常清晰明了的创建对象的方式,当然你也可以用g_object_new函数来创建对象;第三种是设定或取得Boy对象属性成员的值的函数boy_get_*和boy_set_*。正常情况下这三种函数都是一个对象所必需的,另外一个函数boy_info用来显示此对象的当前状态。
宏在GObject系统中用得相当广泛,也相当重要,这里我们定义了两个非常关键的宏,BOY_TYPE宏封装了boy_get_type函数,可以直接取得并替代Boy对象的ID标识;BOY(obj)宏是G_TYPE_CHECK_INSTANCE_CAST宏的再一次封装,目的是将一个Gobject对象强制转换为Boy对象,这在对象的继承中十分关键,也经常用到。
对象的实现
下面的代码实现了上面的Boy对象的定义:
/* boy.c */ #include "boy.h" enum { BOY_BORN, LAST_SIGNAL }; static gint boy_signals[LAST_SIGNAL] = { 0 }; static void boy_cry (void); static void boy_born(void); static void boy_init(Boy *boy); static void boy_class_init(BoyClass *boyclass); GType boy_get_type(void) { static GType boy_type = 0; if(!boy_type) { static const GTypeInfo boy_info = { sizeof(BoyClass), NULL,NULL, (GClassInitFunc)boy_class_init, NULL,NULL, sizeof(Boy), 0, (GInstanceInitFunc)boy_init }; boy_type = g_type_register_static(G_TYPE_OBJECT,"Boy",&boy_info,0); } return boy_type; } static void boy_init(Boy *boy) { boy->age = 0; boy->name = "none"; boy->cry = boy_cry; } static void boy_class_init(BoyClass *boyclass) { boyclass->boy_born = boy_born; boy_signals[BOY_BORN] = g_signal_new("boy_born", BOY_TYPE, G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(BoyClass,boy_born), NULL,NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL); } Boy *boy_new(void) { Boy *boy; boy = g_object_new(BOY_TYPE, NULL); g_signal_emit(boy,boy_signals[BOY_BORN],0); return boy; } int boy_get_age(Boy *boy) { return boy->age; } void boy_set_age(Boy *boy, int age) { boy->age = age; } char *boy_get_name(Boy *boy) { return boy->name; } void boy_set_name(Boy *boy, char *name) { boy->name = name; } Boy* boy_new_with_name(gchar *name) { Boy* boy; boy = boy_new(); boy_set_name(boy, name); return boy; } Boy* boy_new_with_age(gint age) { Boy* boy; boy = boy_new(); boy_set_age(boy, age); return boy; } Boy *boy_new_with_name_and_age(gchar *name, gint age) { Boy *boy; boy = boy_new(); boy_set_name(boy,name); boy_set_age(boy,age); return boy; } static void boy_cry (void) { g_print("The Boy is crying ......\n"); } static void boy_born(void) { g_print("Message : A boy was born .\n"); } void boy_info(Boy *boy) { g_print("The Boy name is %s\n", boy->name); g_print("The Boy age is %d\n", boy->age); }
在这段代码中,出现了实现Boy对象的关键函数,这是在Boy对象的定义中未出现的,也是没必要出现的。就是两个初始化函数,boy_init和boy_class_init,它们分别用来初始化实例结构和类结构。它们并不被在代码中明显调用,关键是将其用宏转换为地址指针,然后赋值到GTypeInfo结构中,然后由GType系统自行处理,同时将它们定义为静态的也是非常必要的。
GTypeInfo结构中定义了对象的类型信息,包括以下内容:
- 包括类结构的长度(必需,即我们定义的BoyClass结构的长度);
- 基础初始化函数(base initialization function,可选);
- 基础结束化函数(base finalization function,可选);
(以上两个函数可以对对象使用的内存来做分配和释放操作,使用时要用GBaseInitFunc和GBaseFinalizeFunc来转换为指针,本例中均未用到,故设为NULL。)
- 类初始化函数(即我们这里的boy_class_init函数,用GclassInit宏来转换,可选,仅用于类和实例类型);
- 类结束函数(可选);
- 实例初始化函数(可选,即我们这里的boy_init函数);
- 最后一个成员是GType变量表(可选)。
定义好GTypeInfo结构后就可以用g_type_register_static函数来注册对象的类型了。
g_type_register_static函数用来注册对象的类型,它的第一个参数是表示此对象的父类的对象类型,我们这里是G_TYPE_OBJECT,这个宏用来表示GObject的父类;第二个参数表示此对象的名称,这里为"Boy";第三个参数是此对象的GTypeInfo结构型指针,这里赋值为&boyinfo;第四个参数是对象注册成功后返回此对象的整型ID标识。
g_object_new函数,用来创建一个基于G_OBJECT的对象,它可以有多个参数,第一个参数是上面说到的已注册的对象标识ID;第二个参数表示后面参数的数量,如果为0,则没有第三个参数;第三个参数开始类型都是GParameter类型,它也是一个结构型,定义为:
struct GParameter{ const gchar* name; GValue value; };
关于GValue,它是变量类型的统一定义,它是基础的变量容器结构,用于封装变量的值和变量的类型,可以GOBJECT文档的GVALUE部分。
信号的定义和应用
在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_gob(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环境下编译通过。
源代码下载: 这里