下面是 Wikipedia 上對類(lèi)型系統(type system)的定義:
GObject 提供了一套運行時(shí)的面向對象的類(lèi)型系統——GType。說(shuō)它是運行時(shí)的類(lèi)型系統,是因為類(lèi)型的定義、檢查、操作、轉換不是在編譯時(shí)進(jìn)行的,而是在程序運行過(guò)程中通過(guò)相關(guān)函數調用來(lái)完成;說(shuō)它是面向對象的,是因為它提供了類(lèi)、接口還有繼承的機制。
目錄[隱藏] |
GType 主要提供了以下幾個(gè)功能:
所謂基本類(lèi)型是指系統自動(dòng)提供的,無(wú)須用戶(hù)自己定義的類(lèi)型。GType 定義了19種基本類(lèi)型:
/* gtype.h */ #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 (20)
可以發(fā)現多數基本類(lèi)型都對應到 C 的基本類(lèi)型,但有幾個(gè)特殊的基本類(lèi)型:G_TYPE_INTERFACE 代表接口類(lèi)型,G_TYPE_PARAM 代表對象屬性的參數說(shuō)明,G_TYPE_OBJECT 代表 GObject 類(lèi)型。關(guān)于 GType 的類(lèi)型有幾點(diǎn)需要強調:
GType 類(lèi)型系統允許注冊類(lèi)、接口,支持聲明某個(gè)類(lèi)實(shí)現某個(gè)接口。由于 GObject 是所有類(lèi)的基類(lèi),所以實(shí)際上在定義、注冊和實(shí)現 GObject 類(lèi)的子類(lèi)這一節中已經(jīng)說(shuō)明了如何注冊類(lèi)了,下面主要講如何定義、注冊和實(shí)現一個(gè)接口。
定義接口
定義接口的方法和定義類(lèi)的方法十分類(lèi)似,主要區別在于接口不能實(shí)例化,所以不需要定義 instance 結構。下面是一個(gè)例子:
#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 的子類(lèi) */ GTypeInterface parent; /* 虛函數列表 */ void (*do_action) (MamanIbaz *self); }; GType maman_ibaz_get_type (void); /* 一般都這樣封裝一下接口提供的函數 */ void maman_ibaz_do_action (MamanIbaz *self);
要理解接口的定義和實(shí)現原理,首先需要理解“虛函數表”的概念,對 C++ 而言,每一個(gè)聲明為 virtual 的成員函數都會(huì )加到類(lèi)的“虛函數表”里面,這樣系統就能保證對象的實(shí)際類(lèi)型調用正確的虛函數實(shí)現,對 Java 而言,所有成員函數默認都是 virtual 的。GType 里面的接口結構其實(shí)就是一個(gè)“虛函數表”,里面保存了一些函數指針,每個(gè)實(shí)現這個(gè)接口的類(lèi)都保存一份獨立的接口結構拷貝,同時(shí)恰當設置里面的函數指針,保證使用的是自己的實(shí)現。
下面是 maman_ibaz_get_type 和 maman_ibaz_do_action 函數的實(shí)現:
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); }
在定義一個(gè)接口的時(shí)候有兩個(gè) tricky 的地方:
注冊接口
在 GType 類(lèi)型系統中,類(lèi)和接口的注冊方法是一樣的,兩者的區別在于對 GTypeInfo 中各個(gè)域的作用。
接口實(shí)現
接口的函數由實(shí)現該接口的類(lèi)提供。實(shí)現接口的類(lèi)在自己的 xxx_get_type 函數里面通過(guò) g_type_add_interface_static 函數來(lái)聲明需要實(shí)現的接口。通過(guò)一個(gè)例子很容易就明白了:
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 函數需要用戶(hù)提供一個(gè)類(lèi)型為 GInterfaceInfo 的參數,該結構定義如下:
struct _GInterfaceInfo { GInterfaceInitFunc interface_init; GInterfaceFinalizeFunc interface_finalize; gpointer interface_data; };
第一個(gè)域是“虛表”的初始化函數,有實(shí)現接口的類(lèi)提供,在這個(gè)函數中需要將虛表中的函數指針指向相應的實(shí)現函數。在上面的例子中,這個(gè)函數就是 baz_interface_init,它的第一個(gè)參數就是需要初始化的虛表。
GType 提供了 API 用于檢查一個(gè)類(lèi)型的各種信息,對用戶(hù)來(lái)說(shuō),比較常用的有幾個(gè):
更多的 API 可以參考 GLib 的手冊。
在定義類(lèi)和接口的時(shí)候,我們都需要定義一個(gè)宏,用于將其他類(lèi)型轉換成我們定義的類(lèi)型,例如 MY_IP_ADDRESS 宏和 MAMAN_IBAZ_TYPE 宏,這些宏在底層都調用 g_type_check_instance_cast 函數,顧名思義,這個(gè)函數首先會(huì )檢查轉換是否合法,這類(lèi)似于 C++ 中的 dynamic_cast。
類(lèi)型轉換在 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 類(lèi)型系統內部,每個(gè)類(lèi)和接口都與一個(gè) 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 */ /* 子類(lèi)列表 */ GType *children; /* 類(lèi)和接口保存的數據是不一樣的 */ TypeData * volatile data; GQuark qname; GData *global_gdata; union { /* 如果是一個(gè)類(lèi),這個(gè) union 保存的是實(shí)現的接口列表 */ IFaceEntry *iface_entries; /* for !iface types */ GType *prerequisistes; } _prot; /* 父類(lèi)數組 */ GType supers[1]; /* flexible array */ };
TypeData 實(shí)際上是一個(gè) union,不同的類(lèi)型對應不同的結構:
union _TypeData { CommonData common; IFaceData iface; ClassData class; InstanceData instance; };
如果類(lèi)型是一個(gè)類(lèi),那么對應到 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; /* 私有數據的大小,通過(guò) g_type_class_add_private 設置 */ guint16 private_size; /* 現在已經(jīng)不用這個(gè)域了 */ 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; };
可以發(fā)現,這兩個(gè)結構的內容基本等同于 GTypeInfo 結構的內容,用戶(hù)指定的類(lèi)型信息大部分是保存在這兩個(gè)結構里面的。
那么,一個(gè)類(lèi)實(shí)現的接口信息又保存在哪里呢?在 TypeNode 結構里面有一個(gè) IFaceEntry 列表,每個(gè) IFaceEntry 都保存該類(lèi)實(shí)現的一個(gè)接口的信息。
struct _IFaceEntry { GType iface_type; /* 類(lèi)實(shí)現的接口的虛表,每個(gè)類(lèi)獨立 */ GTypeInterface *vtable; InitState init_state; };
回憶前面的內容,類(lèi)需要為實(shí)現的接口提供一個(gè)初始化函數,這個(gè)函數是 GInterfaceInfo 結構的一個(gè)域,通過(guò) g_type_add_interface_static 函數來(lái)設置。這個(gè)初始化函數又是怎樣保存的呢?GType 為每一個(gè)接口關(guān)聯(lián)了一個(gè) IFaceHolder 結構的鏈表,每個(gè)表元素記錄一個(gè)實(shí)現該接口的類(lèi)的信息,其中包括該類(lèi)提供的 GInterfaceInfo 結構。
struct _IFaceHolder { GType instance_type; GInterfaceInfo *info; GTypePlugin *plugin; IFaceHolder *next; };
到現在,關(guān)于一個(gè)類(lèi)型的所有信息如何設置、如何保存的問(wèn)題已經(jīng)講清楚了,在類(lèi)的實(shí)例化過(guò)程中將會(huì )用到這些信息。
聯(lián)系客服