[转] 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 类型系统内部的主要数据结构

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;

};

到现在,关于一个类型的所有信息如何设置、如何保存的问题已经讲清楚了,在类的实例化过程中将会用到这些信息。

 

posted on 2012-06-24 17:00  高原  阅读(1376)  评论(0)    收藏  举报

导航