打印
[嵌入式linux]

C的面向对象框架 GObject

[复制链接]
3474|2
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
sinanjj|  楼主 | 2009-4-26 21:48 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
            GObject对象系统 --- 一个C语言的面向对象的框架


GObject对象系统的实质:
    * 一个通用类型系统. 可以注册任意结构类型. 实现组合对象的初始化和内存管理. 类结构, 保持对象的父子关系.
    * 一个基本类型的实现集, 如整型, 枚举型和结构型等.
    * 一个信号系统, 允许用户非常灵活的自定义虚的或重载对象的方法, 并且能充当非常有效力的通知机制.


        类型(GType)与对象(GObject)

GObject是以Gtype为基础实现的一套单根继承的C语言的面向对象的框架.

GType是GLib的类型认证和管理系统. Gtype提供了注册和管理所有基本数据类型和用户定义对象的技术实现.

结构类型分为静态和动态. 静态的类型不能在运行时加载或卸载. 静态类型由g_type_register_static()创建, 通过GTypeInfo结构来取得类型的特殊信息. 动态类型则由 g_type_register_dynamic()创建, 用GTypePlugin结构来取代GTypeInfo, 并且还包括 g_type_plugin_*()系列API. 这些注册函数只运行一次, 目的是取得它们返回的专有类的类型标识.

还可以用g_type_register_fundamental来注册基础类型, 除非你想改动GObject.


        对象的定义

在GObject系统中, 对象由三个部分组成:
    1. 对象的ID标识(唯一, 无符号长整型, 所有此类对象共同的标识).
    2. 对象的类结构(唯一, 结构型, 由对象的所有实例共同拥有).
    3. 对象的实例(多个, 结构型, 对象的具体实现).

基于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);
#endif    /* __BOY_H__*/

这是一段典型的C语言头文件定义, 包括编译预处理, 宏定义, 数据结构定义和函数声明. 首先要看的是两个数据结构对象Boy和BoyClass.
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_*. 第三种是设定或取得Boy对象属性成员的值的函数boy_get_*和boy_set_*.
宏在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 ...... ");
}
static void boy_born(void)
{
    g_print("Message : A boy was born . ");
}

在这段代码中, 两个初始化函数: boy_init和boy_class_init, 它们分别用来初始化实例结构和类结构. 它们并不被在代码中明显调用, 关键是将其用宏转换为地址指针, 然后赋值到GTypeInfo结构中, 然后由GType系统自行处理.

GTypeInfo结构中定义了对象的类型信息, 包括以下内容:
   1. 包括类结构的长度(必需, 即我们定义的BoyClass结构的长度).
   2. 基础初始化函数(base initialization function, 可选).
   3. 基础结束化函数(base finalization function, 可选).
(以上两个函数可以对对象使用的内存来做分配和释放操作, 使用时要用GBaseInitFunc和GBaseFinalizeFunc来转换为指针)
   4. 类初始化函数(即我们这里的boy_class_init函数, 用GclassInit宏来转换, 可选, 仅用于类和实例类型);
   5. 类结束函数(可选);
   6. 实例初始化函数(可选, 即我们这里的boy_init函数);
   7. 最后一个成员是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系统中, 信号是一种定制对象行为的手段. 同时也是一种多种用途的通知机制.
一个对象可以没有信号, 也可以有多个信号. 当有一或多个信号时, 信号的名称定义是必不可少的, 此时C语言的枚举类型的功能就凸显出来了, 用LAST_SIGNAL来表示最后一个信号(不用实现的信号)是一种非常良好的编程风格. 这里为Boy对象定义了一个信号 BOY_BORN, 在对象创建时发出, 表示Boy对象诞生.
同时还需要定义静态的整型指针数组来保存信号的标识, 以便于下一步处理信号时使用.
对象的类结构是所有对象的实例所共有的, 我们将信号也定义在对象的类结构中, 如此信号同样也是所有对象的实例所共有的, 任意一个对象的实例都可以处理信号. 因此我们有必要在在类初始化函数中创建信号. 函数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 ! ");
}
void man_info(Man *man)
{
    g_print("the man name is %s ", BOY(man)->name);
    g_print("the man age is %d ", BOY(man)->age);
    g_print("the man job is %s ", 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`

相关帖子

沙发
gateway| | 2009-5-2 18:12 | 只看该作者

不错,收藏!

使用特权

评论回复
板凳
lhchen922| | 2013-11-4 18:37 | 只看该作者
非常好。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

456

主题

6300

帖子

25

粉丝