Feb 20

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

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

typedef struct _GtkEditable       GtkEditable;         /* Dummy typedef */
typedef struct _GtkEditableClass  GtkEditableClass;

struct _GtkEditableClass
{
  GTypeInterface		   base_iface;

  /* signals */
  void (* insert_text)              (GtkEditable    *editable,
				     const gchar    *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,
				     const gchar    *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实现了该接口。

    完整的代码如下:

#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))

typedef struct _Animal Animal;
typedef struct _AnimalInterface AnimalInterface;


struct _AnimalInterface
{
    GTypeInterface base_iface;

    void (*sleep)(Animal *animal);
    void (*eat)(Animal *animal);
    void (*run)(Animal *animal);
};

void animal_sleep(Animal *animal);
void animal_eat(Animal *animal);
void animal_run(Animal *animal);


static void animal_default_init(AnimalInterface *iface);

G_DEFINE_INTERFACE(Animal,animal,G_TYPE_INVALID);

static void animal_default_init(AnimalInterface *iface)
{

}

void animal_sleep(Animal *animal)
{
    ANIMAL_GET_INTERFACE(animal)->sleep(animal);
}

void animal_eat(Animal *animal)
{
    ANIMAL_GET_INTERFACE(animal)->eat(animal);
}

void animal_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))

typedef struct _Human Human;
typedef struct _HumanInterface HumanInterface;

struct _HumanInterface
{
    GTypeInterface base_iface;

    void (*say)(Human *human);
    void (*study)(Human *human);
};

void human_say(Human *human);
void human_study(Human *human);


static void human_default_init(HumanInterface *iface);

G_DEFINE_INTERFACE(Human,human,TYPE_ANIMAL);

static void human_default_init(HumanInterface *iface)
{
    static gboolean 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;
    }
}

void human_say(Human *human)
{
    HUMAN_GET_INTERFACE(human)->say(human);
}
void human_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))

typedef struct _Boy Boy;
typedef struct _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
};

static void boy_init(Boy *self);
static void boy_class_init(BoyClass *klass);
static void boy_animal_init(AnimalInterface *iface);
static void boy_human_init(HumanInterface *iface);

static void boy_finalize(GObject *object);

static void boy_get_property(GObject *object,guint property_id,GValue *value,GParamSpec *pspec);
static void boy_set_property(GObject *object,guint property_id,const GValue *value,GParamSpec *pspec);

static void boy_sleep(Animal *animal);
static void boy_eat(Animal *animal);
static void boy_run(Animal *animal);

static void boy_say(Human *human);
static void 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));

static void boy_init(Boy *self)
{
    self->name=NULL;
}

static void 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");
}

static void boy_animal_init(AnimalInterface *iface)
{
    iface->sleep=boy_sleep;
    iface->eat=boy_eat;
    iface->run=boy_run;
}

static void boy_human_init(HumanInterface *iface)
{
    iface->say=boy_say;
    iface->study=boy_study;
}

static void boy_finalize(GObject *object)
{
    Boy *boy=BOY(object);

    g_string_free(boy->name,TRUE);

    G_OBJECT_CLASS(g_type_class_peek_parent(object))->finalize;
}

static void 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);

    }
}

static void boy_set_property(GObject *object,guint property_id,const GValue *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);

    }
}

static void boy_sleep(Animal *animal)
{
    g_print("I'm sleeping now!\n");
}

static void boy_eat(Animal *animal)
{
    g_print("I hate eating vegetables!\n");
}

static void boy_run(Animal *animal)
{
    g_print("I will come first!\n");
}

static void boy_say(Human *human)
{
    g_print("Hello!\n");
}

static void 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);


    return object;
}
/**********我是分割线**********/

int main()
{
    GObject *boy;

    g_type_init();

    boy=boy_new_with_name_and_age("Jim",14);

    animal_eat(ANIMAL(boy));
    
    human_study(HUMAN(boy));

    return 0;
}

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

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

typedef struct _Human Human;
typedef struct _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类型系统中添加该接口类型的信号与属性信息。由于任何一个类在实现该接口时,该默认初始化函数都会被调用,所以我们需要用一个变量来确保负责添加信号与属性的函数体只被调用一次:

static void human_default_init(HumanInterface *iface)
{
    static gboolean 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,该宏定义如下:

#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:

#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);
}
void human_study(Human *human)
{
    HUMAN_GET_INTERFACE(human)->study(human);
}

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

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

static void boy_say(Human *human);
static void boy_study(Human *human);

static void boy_human_init(HumanInterface *iface)
{
    iface->say=boy_say;
    iface->study=boy_study;
}

static void boy_say(Human *human)
{
    g_print("Hello!\n");
}

static void boy_study(Human *human)
{
    g_print("I like Mathematics!\n");
}

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

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的定义如下:

#define G_IMPLEMENT_INTERFACE(TYPE_IFACE, iface_init)       { \
  const GInterfaceInfo 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

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

 

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

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

void
g_boxed_type_init (void)
{
  static const 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 */
  };
  const GTypeFundamentalInfo 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类型:

typedef struct _StudentInfo StudentInfo;

struct _StudentInfo
{
	gint number;
	GString *name;
	gdouble grade;
};

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

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;

	return new_std;
}

StudentInfo *student_info_copy(const StudentInfo *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;

	return new_std;
}

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

GType student_info_get_type()
{
    static GType 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);
    }

    return student_info_id;
}


#define TYPE_STUDENT_INFO (student_info_get_type())

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

GType  g_boxed_type_register_static (const gchar *name,
                                     GBoxedCopyFunc boxed_copy,
                                     GBoxedFreeFunc boxed_free);

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

typedef gpointer (*GBoxedCopyFunc) (gpointer boxed);
typedef void (*GBoxedFreeFunc) (gpointer boxed);  

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

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

#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: 该参数为函数指针或函数名(函数指针常量?),其类型应为以下三种之一:
    typedef gpointer (*copy_func) (gpointer boxed);
    typedef TypeName *(*copy_func) (const TypeName *boxed);
    typedef TypeName *(*copy_func) (TypeName *boxed);
  • free_func: 该参数为函数指针或函数名(函数指针常量?),其类型应为以下两种之一:
    typedef void (*free_func) (gpointer boxed);
    typedef void (*free_func) (TypeName *boxed);

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

G_DEFINE_BOXED_TYPE(StudentInfo,student_info,student_info_copy,student_info_free);

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

GType
g_strv_get_type (void)
{
  static volatile 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);
    }

  return g_define_type_id__volatile;
}

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

    完整的测试代码如下:

#include <glib.h>
#include <glib-object.h>
#include <stdlib.h>

typedef struct _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;

	return new_std;
}

void student_info_print(const StudentInfo *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(const StudentInfo *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;

	return new_std;
}

void student_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);

	return 0;
}