大多數現代的計算機語言都帶有本身的類型和對象系統,並附帶算法結構。正象GLib提供的基本類型和算法結構(如鏈表、哈希表等)同樣,GObject的對象系統提供了一種靈活的、可擴展的、並容易映射(到其它語言)的面向對象的C語言框架。它的實質能夠歸納爲: linux
回頁首 算法
GLib中最有特點的是它的對象系統--GObject System,它是以Gtype爲基礎而實現的一套單根繼承的C語言的面向對象的框架。 編程
GType 是GLib 運行時類型認證和管理系統。GType API 是GObject的基礎系統,因此理解GType是理解GObject的關鍵。Gtype提供了註冊和管理全部基本數據類型、用戶定義對象和界面類型的技術實現。(注意:在運用任一GType和GObject函數以前必需運行g_type_init()函數來初始化類型系統。) 數組
爲實現類型定製和註冊這一目的,全部類型必需是靜態的或動態的這兩者之一。靜態的類型永遠不能在運行時加載或卸載,而動態的類型則能夠。靜態類型由g_type_register_static()建立,經過GTypeInfo結構來取得類型的特殊信息。動態類型則由g_type_register_dynamic()建立,用GTypePlugin結構來取代GTypeInfo,而且還包括g_type_plugin_*()系列API。這些註冊函數一般只運行一次,目的是取得它們返回的專有類的類型標識。 數據結構
還能夠用g_type_register_fundamental來註冊基礎類型,它同時須要GTypeInfo和GTypeFundamentalInfo兩個結構,事實上大多數狀況下這是沒必要要的,由於系統預先定義的基礎類型是優於用戶自定義的。 框架
(本文重點介紹建立和使用靜態的類型。) 函數
回頁首 測試
在GObject系統中,對象由三個部分組成: spa
基於GObject的對象究竟是什麼樣的呢?下面是基於GObject的簡單對象 -- Boy的定義代碼: 插件
/* boy.h */ #ifndef __BOY_H__ #define __BOY_H__ #include <glib-object.h> #define BOY_TYPE (boy_get_type()) #define BOY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),BOY_TYPE,Boy)) typedef struct _Boy Boy; typedef struct _BoyClass BoyClass; struct _Boy { GObject parent; // gint age; gchar *name; void (*cry)(void); }; struct _BoyClass { GObjectClass parent_class; // void (*boy_born)(void); }; GType boy_get_type(void); Boy* boy_new(void); int boy_get_age(Boy *boy); void boy_set_age(Boy *boy, int age); char* boy_get_name(Boy *boy); void boy_set_name(Boy *boy, char *name); Boy* boy_new_with_name(gchar *name); Boy* boy_new_with_age(gint age); Boy* boy_new_with_name_and_age(gchar *name, gint age); void boy_info(Boy *boy); #endif /* __BOY_H__*/
這是一段典型的C語言頭文件定義,包括編譯預處理,宏定義,數據結構定義和函數聲明;首先要看的是兩個數據結構對象Boy和BoyClass,
結構類型_Boy是Boy對象的實例,就是說咱們每建立一個Boy對象,也就同時建立了一個Boy結構。Boy對象中的parent表示此對象的父類,GObject系統中全部對象的共同的根都是GObject類,因此這是必須的;其它的成員能夠是公共的,這裏包括表示年齡的age,表示名字的name和表示方法的函數指針cry,外部代碼能夠操做或引用它們。
結構類型_BoyClass是Boy對象的類結構,它是全部Boy對象實例所共有的。BoyClass中的parent_class是GObjectClass,同GObject是全部對象的共有的根同樣,GObejctClass是全部對象的類結構的根。在BoyClass中咱們還定義了一個函數指針boy_born,也就是說這一函數指針也是全部Boy對象實例共有的,全部的Boy實例均可以調用它;一樣,若是須要的話,你也能夠在類結構中定義其它數據成員。
其他的函數定義包括三種,一種是取得Boy對象的類型ID的函數boy_get_type,這是必須有的;另外一種是建立Boy對象實例的函數boy_new和boy_new_with_*,這是很是清晰明瞭的建立對象的方式,固然你也能夠用g_object_new函數來建立對象;第三種是設定或取得Boy對象屬性成員的值的函數boy_get_*和boy_set_*。正常狀況下這三種函數都是一個對象所必需的,另一個函數boy_info用來顯示此對象的當前狀態。
宏在GObject系統中用得至關普遍,也至關重要,這裏咱們定義了兩個很是關鍵的宏,BOY_TYPE宏封裝了boy_get_type函數,能夠直接取得並替代Boy對象的ID標識;BOY(obj)宏是G_TYPE_CHECK_INSTANCE_CAST宏的再一次封裝,目的是將一個Gobject對象強制轉換爲Boy對象,這在對象的繼承中十分關鍵,也常常用到。
下面的代碼實現了上面的Boy對象的定義:
/* boy.c */ #include "boy.h" enum { BOY_BORN, LAST_SIGNAL }; static gint boy_signals[LAST_SIGNAL] = { 0 }; static void boy_cry (void); static void boy_born(void); static void boy_init(Boy *boy); static void boy_class_init(BoyClass *boyclass); GType boy_get_type(void) { static GType boy_type = 0; if(!boy_type) { static const GTypeInfo boy_info = { sizeof(BoyClass), NULL,NULL, (GClassInitFunc)boy_class_init, NULL,NULL, sizeof(Boy), 0, (GInstanceInitFunc)boy_init }; boy_type = g_type_register_static(G_TYPE_OBJECT,"Boy",&boy_info,0); } return boy_type; } static void boy_init(Boy *boy) { boy->age = 0; boy->name = "none"; boy->cry = boy_cry; } static void boy_class_init(BoyClass *boyclass) { boyclass->boy_born = boy_born; boy_signals[BOY_BORN] = g_signal_new("boy_born", BOY_TYPE, G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET(BoyClass,boy_born), NULL,NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0, NULL); } Boy *boy_new(void) { Boy *boy; boy = g_object_new(BOY_TYPE, NULL); g_signal_emit(boy,boy_signals[BOY_BORN],0); return boy; } int boy_get_age(Boy *boy) { return boy->age; } void boy_set_age(Boy *boy, int age) { boy->age = age; } char *boy_get_name(Boy *boy) { return boy->name; } void boy_set_name(Boy *boy, char *name) { boy->name = name; } Boy* boy_new_with_name(gchar *name) { Boy* boy; boy = boy_new(); boy_set_name(boy, name); return boy; } Boy* boy_new_with_age(gint age) { Boy* boy; boy = boy_new(); boy_set_age(boy, age); return boy; } Boy *boy_new_with_name_and_age(gchar *name, gint age) { Boy *boy; boy = boy_new(); boy_set_name(boy,name); boy_set_age(boy,age); return boy; } static void boy_cry (void) { g_print("The Boy is crying ......\n"); } static void boy_born(void) { g_print("Message : A boy was born .\n"); } void boy_info(Boy *boy) { g_print("The Boy name is %s\n", boy->name); g_print("The Boy age is %d\n", boy->age); }
在這段代碼中,出現了實現Boy對象的關鍵函數,這是在Boy對象的定義中未出現的,也是不必出現的。就是兩個初始化函數,boy_init和boy_class_init,它們分別用來初始化實例結構和類結構。它們並不被在代碼中明顯調用,關鍵是將其用宏轉換爲地址指針,而後賦值到GTypeInfo結構中,而後由GType系統自行處理,同時將它們定義爲靜態的也是很是必要的。
GTypeInfo結構中定義了對象的類型信息,包括如下內容:
(以上兩個函數能夠對對象使用的內存來作分配和釋放操做,使用時要用GBaseInitFunc和GBaseFinalizeFunc來轉換爲指針,本例中均未用到,故設爲NULL。)
定義好GTypeInfo結構後就能夠用g_type_register_static函數來註冊對象的類型了。
g_type_register_static函數用來註冊對象的類型,它的第一個參數是表示此對象的父類的對象類型,咱們這裏是G_TYPE_OBJECT,這個宏用來表示GObject的父類;第二個參數表示此對象的名稱,這裏爲"Boy";第三個參數是此對象的GTypeInfo結構型指針,這裏賦值爲&boyinfo;第四個參數是對象註冊成功後返回此對象的整型ID標識。
g_object_new函數,用來建立一個基於G_OBJECT的對象,它能夠有多個參數,第一個參數是上面說到的已註冊的對象標識ID;第二個參數表示後面參數的數量,若是爲0,則沒有第三個參數;第三個參數開始類型都是GParameter類型,它也是一個結構型,定義爲:
struct GParameter{ const gchar* name; GValue value; };
關於GValue,它是變量類型的統必定義,它是基礎的變量容器結構,用於封裝變量的值和變量的類型,能夠GOBJECT文檔的GVALUE部分。
在GObject系統中,信號是一種定製對象行爲的手段,同時也是一種多種用途的通知機制。初學者多是在GTK+中首先接觸到信號這一律唸的,事實上在普通的字符界面編程中也能夠正常應用,這多是不少初學者不曾想到的。
一個對象能夠沒有信號,也能夠有多個信號。當有一或多個信號時,信號的名稱定義是必不可少的,此時C語言的枚舉類型的功能就凸顯出來了,用LAST_SIGNAL來表示最後一個信號(不用實現的信號)是一種很是良好的編程風格。這裏爲Boy對象定義了一個信號BOY_BORN,在對象建立時發出,表示Boy對象誕生。
同時還須要定義靜態的整型指針數組來保存信號的標識,以便於下一步處理信號時使用。
對象的類結構是全部對象的實例所共有的,咱們將信號也定義在對象的類結構中,如此信號一樣也是全部對象的實例所共有的,任意一個對象的實例均可以處理信號。所以咱們有必要在在類初始化函數中建立信號(這也多是GObject設計者的初衷)。函數g_signal_new用來建立一個新的信號,它的詳細使用方法能夠在GObject的API文檔中找到。信號建立成功後,返回一個信號的標識ID,如此就能夠用發射信號函數g_signal_emit向指定義對象的實例發射信號,從而執行相應的功能。
本例中每建立一個新的Boy對象,就會發射一次BOY_BORN信號,也就會執行一次咱們定義的boy_born函數,也就輸出一行"Message : A boy was born ."信息。
對象實例全部的屬性和方法通常都定義在對象的實例結構中,屬性定義爲變量或變量指針,而方法則定義爲函數指針,如此,咱們必定要定義函數爲static類型,當爲函數指針賦值時,纔能有效。
如下爲繼承自Boy對象的Man對象的實現,Man對象在Boy對象的基礎上又增長了一個屬性job和一個方法bye。
#ifndef __MAN_H__ #define __MAN_H__ #include "boy.h" #define MAN_TYPE (man_get_type()) #define MAN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),MAN_TYPE,Man)) typedef struct _Man Man; typedef struct _ManClass ManClass; struct _Man { Boy parent; char *job; void (*bye)(void); }; struct _ManClass { BoyClass parent_class; }; GType man_get_type(void); Man* man_new(void); gchar* man_get_gob(Man *man); void man_set_job(Man *man, gchar *job); Man* man_new_with_name_age_and_job(gchar *name, gint age, gchar *job); void man_info(Man *man); #endif //__MAN_H__ /* man.c */ #include "man.h" static void man_bye(void); static void man_init(Man *man); static void man_class_init(Man *man); GType man_get_type(void) { static GType man_type = 0; if(!man_type) { static const GTypeInfo man_info = { sizeof(ManClass), NULL, NULL, (GClassInitFunc)man_class_init, NULL, NULL, sizeof(Man), 0, (GInstanceInitFunc)man_init }; man_type = g_type_register_static(BOY_TYPE, "Man", &man_info, 0); } return man_type; } static void man_init(Man *man) { man->job = "none"; man->bye = man_bye; } static void man_class_init(Man *man) { } Man* man_new(void) { Man *man; man = g_object_new(MAN_TYPE, 0); return man; } gchar* man_get_gob(Man *man) { return man->job; } void man_set_job(Man *man, gchar *job) { man->job = job; } Man* man_new_with_name_age_and_job(gchar *name, gint age, gchar *job) { Man *man; man = man_new(); boy_set_name(BOY(man), name); boy_set_age(BOY(man), age); man_set_job(man, job); return man; } static void man_bye(void) { g_print("Goodbye everyone !\n"); } void man_info(Man *man) { g_print("the man name is %s\n", BOY(man)->name); g_print("the man age is %d\n", BOY(man)->age); g_print("the man job is %s\n", man->job); }
關鍵在於定義對象時將父對象實例定義爲Boy,父類設定爲BoyClass,在註冊此對象時將其父對象類型設爲BOY_TYPE,在設定對象屬性時如用到父對象的屬性要強制轉換下,如取得對象的name屬性,就必須用BOY(obj)->name,由於Man自己沒有name屬性,而其父對象Boy有,因此用BOY宏將其強制爲Boy類型的對象。
#include <glib.h> #include "boy.h" #include "man.h" int main(int argc, char *argv[]) { Boy *tom, *peter; Man *green, *brown; g_type_init();//注意,初始化類型系統,必需 tom = boy_new_with_name("Tom"); tom->cry(); boy_info(tom); peter = boy_new_with_name_and_age("Peter", 10); peter->cry(); boy_info(peter); green = man_new(); boy_set_name(BOY(green), "Green"); //設定Man對象的name屬性用到其父對象Boy的方法 boy_set_age(BOY(green), 28); man_set_job(green, "Doctor"); green->bye(); man_info(green); brown = man_new_with_name_age_and_job("Brown", 30, "Teacher"); brown->bye(); man_info(brown); } Makefile文件以下: CC = gcc all: $(CC) -c boy.c `pkg-config --cflags glib-2.0 gobject-2.0` $(CC) -c man.c `pkg-config --cflags glib-2.0 gobject-2.0` $(CC) -c main.c `pkg-config --cflags glib-2.0 gobject-2.0` $(CC) -o simple boy.o man.o main.o `pkg-config --libs glib-2.0 gobject-2.0` 執行make命令編譯,編譯結束後,執行./simple運行此測試程序,輸出結果以下: Message : A boy was born . The Boy is crying ...... The Boy name is Tom The Boy age is 0 Message : A boy was born . The Boy is crying ...... The Boy name is Peter The Boy age is 10 Goodbye everyone ! the man name is Green the man age is 28 the man job is Doctor Goodbye everyone ! the man name is Brown the man age is 30 the man job is Teacher
Makefile中用到`pkg-config -cflags -libs gobject-2.0`,在GLIB中將線程(gthread),插件(gmoudle)和對象系統(gobject)這三個子系統區別對待,編譯時要注意加入相應的參數。
本文只是概要的介紹瞭如何定義和實現GObject對象,GObject系統中還有不少相關內容,如:枚舉和標識類型(Enumeration and flags types);Gboxed,是Gtype系統中註冊一種封裝爲不透明的C語言結構類型的機制;許多對象用到的參數對象都是C結構類型,使用者沒必要了解其結構的內部定義,即不透明,GBoxed便是實現這一功能的機制;標準的參數和變量類型的定義(Standard Parameter and Value Types)等,它們都以C語言來開發,是深刻了解和掌握GObject的關鍵。
透過以上代碼實現,咱們還能夠看出,以GLIB爲基礎的GTK+/GNOME開發環境所具備的獨特的編程風格和獨到的開發思想。這一點在長期的編程實踐中會體驗得更深入。
有了GObject系統這一基礎,GTK+經過它將X窗口環境中的控件(Widget)巧妙的封裝起來,這使開發LINUX平臺上的GUI應用程序更方便,更快捷。
以上代碼在Redhat 8.0 Linux平臺,GLIB2.2.1環境下編譯經過。