知趣的造句:GType 类型系统 - 清华大学开源软件俱乐部 THOSS
来源:百度文库 编辑:中财网 时间:2024/04/28 14:12:34
GType 类型系统
出自清华大学开源软件俱乐部 THOSS
跳转到: 导航, 搜索下面是 Wikipedia 上对类型系统(type system)的定义:
- In computer science, a type system defines how a programming language classifies values and expressions into types, how it can manipulate those types and how they interact.
GObject 提供了一套运行时的面向对象的类型系统——GType。说它是运行时的类型系统,是因为类型的定义、检查、操作、转换不是在编译时进行的,而是在程序运行过程中通过相关函数调用来完成;说它是面向对象的,是因为它提供了类、接口还有继承的机制。
目录
[隐藏]- 1 GType 类型系统的功能
- 1.1 基本类型
- 1.2 类和接口
- 1.3 查看类型信息
- 1.4 类型转换
GType 类型系统的功能
GType 主要提供了以下几个功能:
- 基本类型
- 类和接口
- 查看类型信息,如父类、子类和实现的接口列表
- 类型的向上和向下转换,类型检查
基本类型
所谓基本类型是指系统自动提供的,无须用户自己定义的类型。GType 定义了19种基本类型:
/* gtype.h */ #define G_TYPE_INTERFACEG_TYPE_MAKE_FUNDAMENTAL (2) #define G_TYPE_CHARG_TYPE_MAKE_FUNDAMENTAL (3) #define G_TYPE_UCHARG_TYPE_MAKE_FUNDAMENTAL (4) #define G_TYPE_BOOLEANG_TYPE_MAKE_FUNDAMENTAL (5) #define G_TYPE_INTG_TYPE_MAKE_FUNDAMENTAL (6) #define G_TYPE_UINTG_TYPE_MAKE_FUNDAMENTAL (7) #define G_TYPE_LONGG_TYPE_MAKE_FUNDAMENTAL (8) #define G_TYPE_ULONGG_TYPE_MAKE_FUNDAMENTAL (9) #define G_TYPE_INT64G_TYPE_MAKE_FUNDAMENTAL (10) #define G_TYPE_UINT64G_TYPE_MAKE_FUNDAMENTAL (11) #define G_TYPE_ENUMG_TYPE_MAKE_FUNDAMENTAL (12) #define G_TYPE_FLAGSG_TYPE_MAKE_FUNDAMENTAL (13) #define G_TYPE_FLOATG_TYPE_MAKE_FUNDAMENTAL (14) #define G_TYPE_DOUBLEG_TYPE_MAKE_FUNDAMENTAL (15) #define G_TYPE_STRINGG_TYPE_MAKE_FUNDAMENTAL (16) #define G_TYPE_POINTERG_TYPE_MAKE_FUNDAMENTAL (17) #define G_TYPE_BOXEDG_TYPE_MAKE_FUNDAMENTAL (18) #define G_TYPE_PARAMG_TYPE_MAKE_FUNDAMENTAL (19) #define G_TYPE_OBJECTG_TYPE_MAKE_FUNDAMENTAL (20)
可以发现多数基本类型都对应到 C 的基本类型,但有几个特殊的基本类型:G_TYPE_INTERFACE 代表接口类型,G_TYPE_PARAM 代表对象属性的参数说明,G_TYPE_OBJECT 代表 GObject 类型。关于 GType 的类型有几点需要强调:
- 每一个类型有一个C语言类型为 GType 的唯一的标识符,这个标识符其实就是一个整形,上面这些宏返回的就是一个 GType,自定义的类(如 MY_IP_ADDRESS,也是如此);
- 这个标识符用的不多,但是有些场合需要指定类型,就需要用到它:
- new 一个对象的时候;
- 指定对象属性的类型;
- 指定 GValue 值的类型;
- 声明一个类实现某个接口;
- 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; /* dummy object */ typedef struct _MamanIbazInterface MamanIbazInterface; /* 接口结构 */ struct _MamanIbazInterface { /* 所有接口都是 GTypeInterface 的子类 */ GTypeInterface parent; /* 虚函数列表 */ 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, /* base_init */ NULL, /* base_finalize */ NULL, /* class_init */ NULL, /* class_finalize */ NULL, /* class_data */ sizeof (MamanBaz), 0, /* n_preallocs */ NULL /* instance_init */ }; static const GInterfaceInfo ibaz_info = { (GInterfaceInitFunc) baz_interface_init, /* interface_init */ NULL, /* interface_finalize */ NULL /* interface_data */ }; 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 类型系统内部的主要数据结构
在 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;/* combines some common path checks */ /* 子类列表 */ GType *children; /* 类和接口保存的数据是不一样的 */ TypeData * volatile data; GQuark qname; GData *global_gdata; union { /* 如果是一个类,这个 union 保存的是实现的接口列表 */ IFaceEntry *iface_entries;/* for !iface types */ GType *prerequisistes; } _prot; /* 父类数组 */ GType supers[1]; /* flexible array */ };
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; /* 私有数据的大小,通过 g_type_class_add_private 设置 */ guint16 private_size; /* 现在已经不用这个域了 */ guint16 n_preallocs; GInstanceInitFunc instance_init; }; struct _IFaceData { CommonData common; /* 用 GTypeInfo 里面的 class_size 域赋值 */ guint16 vtable_size; /* 用 base_init 域赋值 */ GBaseInitFunc vtable_init_base; /* 用 base_finalize 域赋值 */ GBaseFinalizeFunc vtable_finalize_base; /* 用 class_init 域赋值 */ GClassInitFunc dflt_init; /* 用 class_finalize 域赋值 */ 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; };
到现在,关于一个类型的所有信息如何设置、如何保存的问题已经讲清楚了,在类的实例化过程中将会用到这些信息。
取自"http://www.thoss.org.cn/mediawiki/index.php/GType_%E7%B1%BB%E5%9E%8B%E7%B3%BB%E7%BB%9F"