[转] GType 类型系统的功能
GType 类型系统的功能
GType 主要提供了以下几个功能:
· 基本类型
· 类和接口
· 查看类型信息,如父类、子类和实现的接口列表
· 类型的向上和向下转换,类型检查
所谓基本类型是指系统自动提供的,无须用户自己定义的类型。GType 定义了19种基本类型:
(1) #define G_TYPE_INTERFACE G_TYPE_MAKE_FUNDAMENTAL
(2) #define G_TYPE_CHAR G_TYPE_MAKE_FUNDAMENTAL
(3) #define G_TYPE_UCHAR G_TYPE_MAKE_FUNDAMENTAL
(4) #define G_TYPE_BOOLEAN G_TYPE_MAKE_FUNDAMENTAL
(5) #define G_TYPE_INT G_TYPE_MAKE_FUNDAMENTAL
(6) #define G_TYPE_UINT G_TYPE_MAKE_FUNDAMENTAL
(7) #define G_TYPE_LONG G_TYPE_MAKE_FUNDAMENTAL
(8) #define G_TYPE_ULONG G_TYPE_MAKE_FUNDAMENTAL
(9) #define G_TYPE_INT64 G_TYPE_MAKE_FUNDAMENTAL
(10) #define G_TYPE_UINT64 G_TYPE_MAKE_FUNDAMENTAL
(11) #define G_TYPE_ENUM G_TYPE_MAKE_FUNDAMENTAL
(12) #define G_TYPE_FLAGS G_TYPE_MAKE_FUNDAMENTAL
(13) #define G_TYPE_FLOAT G_TYPE_MAKE_FUNDAMENTAL
(14) #define G_TYPE_DOUBLE G_TYPE_MAKE_FUNDAMENTAL
(15) #define G_TYPE_STRING G_TYPE_MAKE_FUNDAMENTAL
(16) #define G_TYPE_POINTER G_TYPE_MAKE_FUNDAMENTAL
(17) #define G_TYPE_BOXED G_TYPE_MAKE_FUNDAMENTAL
(18) #define G_TYPE_PARAM G_TYPE_MAKE_FUNDAMENTAL
(19) #define G_TYPE_OBJECT G_TYPE_MAKE_FUNDAMENTAL
可以发现多数基本类型都对应到 C 的基本类型,但有几个特殊的基本类型:G_TYPE_INTERFACE 代表接口类型,G_TYPE_PARAM 代表对象属性的参数说明,G_TYPE_OBJECT 代表 GObject 类型。关于 GType 的类型有几点需要强调:
· 每一个类型有一个C语言类型为 GType 的唯一的标识符,这个标识符其实就是一个整型,上面这些宏返回的就是一个 GType,自定义的类(如 MY_IP_ADDRESS,也是如此);
· 这个标识符用的不多,但是有些场合需要指定类型,就需要用到它:
1. new 一个对象的时候;
2. 指定对象属性的类型;
3. 指定 GValue 值的类型;
4. 声明一个类实现某个接口;
5. GType 系统内部。
GType 类型系统允许注册类、接口,支持声明某个类实现某个接口。由于 GObject 是所有类的基类,所以实际上在定义、注册和实现 GObject 类的子类这一节中已经说明了如何注册类了,下面主要讲如何定义、注册和实现一个接口。
定义接口
定义接口的方法和定义类的方法十分类似,主要区别在于接口不能实例化,所以不需要定义 instance 结构。下面是一个例子:
#define MAMAN_IBAZ_TYPE (maman_ibaz_get_type ())
#define MAMAN_IBAZ(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MAMAN_IBAZ_TYPE, MamanIbaz))
#define MAMAN_IS_IBAZ(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MAMAN_IBAZ_TYPE))
#define MAMAN_IBAZ_GET_INTERFACE(inst) (G_TYPE_INSTANCE_GET_INTERFACE ((inst), MAMAN_IBAZ_TYPE, MamanIbazInterface))
typedef struct _MamanIbaz MamanIbaz;
typedef struct _MamanIbazInterface MamanIbazInterface;
struct _MamanIbazInterface
{
GTypeInterface paren ;
void (*do_action) (MamanIbaz *self);
};
GType maman_ibaz_get_type (void);
void maman_ibaz_do_action (MamanIbaz *self);
要理解接口的定义和实现原理,首先需要理解“虚函数表”的概念,对 C++ 而言,每一个声明为 virtual 的成员函数都会加到类的“虚函数表”里面,这样系统就能保证对象的实际类型调用正确的虚函数实现,对 Java 而言,所有成员函数默认都是 virtual 的。GType 里面的接口结构其实就是一个“虚函数表”,里面保存了一些函数指针,每个实现这个接口的类都保存一份独立的接口结构拷贝,同时恰当设置里面的函数指针,保证使用的是自己的实现。
下面是 maman_ibaz_get_type 和 maman_ibaz_do_action 函数的实现:
GType maman_ibaz_get_type ()
{
static GType type = 0;
if (!type)
{
type = g_type_register_static_simple (G_TYPE_INTERFACE, "MamanIbaz", sizeof (MamanIbazInterface), (GClassInitFunc) maman_ibaz_class_init, 0, NULL, 0);
}
return type;
}
void maman_ibaz_do_action (MamanIbaz *self)
{
MAMAN_IBAZ_GET_INTERFACE (self)->do_action (self);
}
在定义一个接口的时候有两个 tricky 的地方:
· MamanIbaz 结构并没有被定义,而只是声明。在使用接口的函数时,假设类 MamanBaz 实现了接口 MamanIbaz,那么传给函数的指针应该是 MAMAN_IBAZ(ptr_to_a_mamanbaz_object)。这和面向对象中接口的概念是一致的,实现了某个接口的类的对象能向上转换成接口的对象。
· 在 maman_ibaz_do_action 函数的实现中,需要先通过 MAMAN_IBAZ_GET_INTERFACE (self) 获得一个接口结构(虚函数表),然后再调用虚表里面的 do_action 函数,由于 self 是由实现这个接口的对象转换来的,所以这就能保证执行的 do_action 函数是 self 对象的类所重载的版本。
注册接口
在 GType 类型系统中,类和接口的注册方法是一样的,两者的区别在于对 GTypeInfo 中各个域的作用。
· base_init:一般都设为 NULL
· base_finalize:一般都设为 NULL
· class_init:接口的默认初始化函数,如果实现该接口的类不提供初始化接口(虚表)的函数的话,那么将由这个函数进行初始化,很多接口也直接设为 NULL
· class_finalize:和 class_init 相反
接口实现
接口的函数由实现该接口的类提供。实现接口的类在自己的 xxx_get_type 函数里面通过 g_type_add_interface_static 函数来声明需要实现的接口。通过一个例子很容易就明白了:
static void maman_baz_do_action (MamanIbaz *self)
{
g_print ("Baz implementation of IBaz interface Action.\n");
}
static void baz_interface_init (gpointer g_iface, gpointer iface_data)
{
MamanIbazInterface *iface = (MamanIbazInterface *)g_iface;
iface->do_action = maman_baz_do_action;
}
GType maman_baz_get_type (void)
{
static GType type = 0;
if (type == 0)
{
static const GTypeInfo info = {
sizeof (MamanBazInterface), NULL, NULL, NULL, NULL, NULL, sizeof (MamanBaz), 0, NULL
};
static const GInterfaceInfo ibaz_info = {
(GInterfaceInitFunc) baz_interface_init, NULL, NULL
};
type = g_type_register_static (G_TYPE_OBJECT, "MamanBazType", &info, 0);
g_type_add_interface_static (type, MAMAN_IBAZ_TYPE, &ibaz_info);
}
return type;
}
g_type_add_interface_static 函数需要用户提供一个类型为 GInterfaceInfo 的参数,该结构定义如下:
struct _GInterfaceInfo { GInterfaceInitFunc interface_init; GInterfaceFinalizeFunc interface_finalize; gpointer interface_data; };
第一个域是“虚表”的初始化函数,有实现接口的类提供,在这个函数中需要将虚表中的函数指针指向相应的实现函数。在上面的例子中,这个函数就是 baz_interface_init,它的第一个参数就是需要初始化的虚表。
GType 提供了 API 用于检查一个类型的各种信息,对用户来说,比较常用的有几个:
· g_type_parent:获取父类的类型
· g_type_class_ref:返回类的 class 结构
· g_type_interface_peek:返回类所实现的某个接口的接口结构(虚表)
· g_type_children:返回子类列表
· g_type_interfaces:返回实现的接口列表
更多的 API 可以参考 GLib 的手册。
在定义类和接口的时候,我们都需要定义一个宏,用于将其他类型转换成我们定义的类型,例如 MY_IP_ADDRESS 宏和 MAMAN_IBAZ_TYPE 宏,这些宏在底层都调用 g_type_check_instance_cast 函数,顾名思义,这个函数首先会检查转换是否合法,这类似于 C++ 中的 dynamic_cast。
类型转换在 Gtk+ 的代码中有大量的应用,例如下面的代码:
int main (int argc, char *argv[])
{
GtkWidget *window, *ipaddress;
gint address[4] = { 1, 20, 35, 255 };
gtk_init (&argc, &argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "GtkIPAddress");
gtk_container_set_border_width (GTK_CONTAINER (window), 10);
g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL);
ipaddress = my_ip_address_new ();
my_ip_address_set_address (MY_IP_ADDRESS (ipaddress), address);
g_signal_connect (G_OBJECT (ipaddress), "ip-changed", G_CALLBACK (ip_address_changed), NULL);
gtk_container_add (GTK_CONTAINER (window), ipaddress);
gtk_widget_show_all (window);
gtk_main ();
return 0;
}
在 GType 类型系统内部,每个类和接口都与一个 TypeNode 数据结构对应。
struct _TypeNode {
GTypePlugin *plugin;
guint n_children : 12;
guint n_supers : 8;
guint _prot_n_ifaces_prerequisites : 9;
guint is_classed : 1;
guint is_instantiatable : 1;
guint mutatable_check_cache : 1;
GType *children;
TypeData * volatile data;
GQuark qname;
GData *global_gdata;
union {
IFaceEntry *iface_entries;
GType *prerequisistes;
} _prot;
GType supers[1];
};
TypeData 实际上是一个 union,不同的类型对应不同的结构:
union _TypeData
{
CommonData common;
IFaceData iface;
ClassData class;
InstanceData instance;
};
如果类型是一个类,那么对应到 InstanceData,如果是接口,对应到 IFaceData:
struct _InstanceData
{
CommonData common;
guint16 class_size;
guint init_state : 4;
GBaseInitFunc class_init_base;
GBaseFinalizeFunc class_finalize_base;
GClassInitFunc class_init;
GClassFinalizeFunc class_finalize;
gconstpointer class_data;
gpointer class;
guint16 instance_size;
guint16 private_size;
guint16 n_preallocs;
GInstanceInitFunc instance_init;
};
struct _IFaceData
{
CommonData common;
guint16 vtable_size;
GBaseInitFunc vtable_init_base;
GBaseFinalizeFunc vtable_finalize_base;
GClassInitFunc dflt_init;
GClassFinalizeFunc dflt_finalize;
gconstpointer dflt_data;
gpointer dflt_vtable;
};
可以发现,这两个结构的内容基本等同于 GTypeInfo 结构的内容,用户指定的类型信息大部分是保存在这两个结构里面的。
那么,一个类实现的接口信息又保存在哪里呢?在 TypeNode 结构里面有一个 IFaceEntry 列表,每个 IFaceEntry 都保存该类实现的一个接口的信息。
struct _IFaceEntry
{
GType iface_type;
GTypeInterface *vtable;
InitState init_state;
};
回忆前面的内容,类需要为实现的接口提供一个初始化函数,这个函数是 GInterfaceInfo 结构的一个域,通过 g_type_add_interface_static 函数来设置。这个初始化函数又是怎样保存的呢?GType 为每一个接口关联了一个 IFaceHolder 结构的链表,每个表元素记录一个实现该接口的类的信息,其中包括该类提供的 GInterfaceInfo 结构。
struct _IFaceHolder
{
GType instance_type;
GInterfaceInfo *info;
GTypePlugin *plugin;
IFaceHolder *next;
};
到现在,关于一个类型的所有信息如何设置、如何保存的问题已经讲清楚了,在类的实例化过程中将会用到这些信息。
浙公网安备 33010602011771号