在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则是该类针对该接口的初始化函数。