歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux資訊 >> 更多Linux

GObject對象系統

內容:
前言
類型(GType)與對象(GObject)
對象的定義
對象的實現
信號的定義和應用
對象的屬性和方法
對象的繼承
測試我們定義的對象
感謝
參考資料
關於作者
對於本文的評價
相關內容:
淺析 GLib
在 Linux 專區還有:
教程
工具與產品
代碼與組件
項目
文章

宋國偉 ([email protected]) 鄉村小學英語教師
2003 年 8 月

簡單的說,GObject對象系統是一個建立在GLIB基礎上的,用C語言完成的,具有跨平台特色的、靈活的、可擴展的、非常容易映射到其它語言的面向對象的框架。如果你是一個C語言的執著的追隨者,你沒有理由不研究一下它。

前言
大多數現代的計算機語言都帶有自己的類型和對象系統,並附帶算法結構。正象GLib提供的基本類型和算法結構(如鏈表、哈希表等)一樣,GObject的對象系統提供了一種靈活的、可擴展的、並容易映射(到其它語言)的面向對象的C語言框架。它的實質可以概括為:

* 一個通用類型系統,用來注冊任意的、輕便的、單根繼承的、並能推導出任意深度的結構類型的界面,它照顧組合對象的定制、初始化和內存管理,類結構,保持對象的父子關系,處理這些類型的動態實現。也就是說,這些類型的實現是在運行時重置和卸載的;

* 一個基本類型的實現集,如整型,枚舉型和結構型等;
* 一個基本對象體系之上的基本對象類型的實現的例子--GObject基本類型;
* 一個信號系統,允許用戶非常靈活的自定義虛的或重載對象的方法,並且能充當非常有效力的通知機制;
* 一個可擴展的參數/變量體系,支持所有的能被用作處理對象屬性或其它參數化類型的基本的類型。

類型(GType)與對象(GObject)
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系統中,對象由三個部分組成:

1. 對象的ID標識(唯一,無符號長整型,所有此類對象共同的標識);
2. 對象的類結構(唯一,結構型,由對象的所有實例共同擁有);
3. 對象的實例(多個,結構型,對象的具體實現)。

基於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 ......
");
}

static void boy_born(void)
{
g_print("Message : A boy was born .
");
}



void boy_info(Boy *boy)
{
g_print("The Boy name is %s
", boy->name);
g_print("The Boy age is %d
", boy->age);
}

在這段代碼中,出現了實現Boy對象的關鍵函數,這是在Boy對象的定義中未出現的,也是沒必要出現的。就是兩個初始化函數,boy_init和 boy_class_init,它們分別用來初始化實例結構和類結構。它們並不被在代碼中明顯調用,關鍵是將其用宏轉換為地址指針,然後賦值到 GTypeInfo結構中,然後由GType系統自行處理,同時將它們定義為靜態的也是非常必要的。

GTypeInfo結構中定義了對象的類型信息,包括以下內容:

1. 包括類結構的長度(必需,即我們定義的BoyClass結構的長度);
2. 基礎初始化函數(base initialization function,可選);
3. 基礎結束化函數(base finalization function,可選);

(以上兩個函數可以對對象使用的內存來做分配和釋放操作,使用時要用GBaseInitFunc和GBaseFinalizeFunc來轉換為指針,本例中均未用到,故設為NULL。)
4. 類初始化函數(即我們這裡的boy_class_init函數,用GclassInit宏來轉換,可選,僅用於類和實例類型);
5. 類結束函數(可選);
6. 實例初始化函數(可選,即我們這裡的boy_init函數);
7. 最後一個成員是GType變量表(可選)。

定義好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 !
");
}

void man_info(Man *man)
{
g_print("the man name is %s
", BOY(man)->name);
g_print("the man age is %d
", BOY(man)->age);
g_print("the man job is %s
", 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環境下編譯通過。

感謝
本文的寫作參考了中國LINUX論壇的網友hoyt的文章,可以在gtkvb.cffd.org.cn找到,在此表示感謝。

參考資料

* 本文的所有代碼可以在這裡下載。
* Thomas Hunger寫的文章:Gobject tutorial
* 中國 linux 論壇的網友 hoyt 的網頁 http://gtkvb.cosoft.org.cn/ 上的文章
* 與本文相關的另一篇文章《淺析GLib》

 
關於作者
宋國偉,鄉村小學英語教師,他是《GTK+2.0編程范例》(清華大學出版社出版)一書的作者,業余時間致力於用GTK+開發LINUX GUI應用程序,可以通過電子郵件地址 [email protected] 與他聯系。



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環境下編譯通過。 感謝 本文的寫作參考了中國LINUX論壇的網友hoyt的文章,可以在gtkvb.cffd.org.cn找到,在此表示感謝。 參考資料 * 本文的所有代碼可以在這裡下載。

* Thomas Hunger寫的文章:Gobject tutorial * 中國 linux 論壇的網友 hoyt 的網頁 http://gtkvb.cosoft.org.cn/ 上的文章 * 與本文相關的另一篇文章《淺析GLib》   關於作者 宋國偉,鄉村小學英語教師,他是《GTK+2.0編程范例》(清華大學出版社出版)一書的作者,業余時間致力於用GTK+開發LINUX GUI應用程序,可以通過電子郵件地址 [email protected] 與他聯系。 返回前一頁 本站由張善友制作並維護 copyright 2002-2003 All rights reserved



本文只是概要的介紹了如何定義和實現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環境下編譯通過。 感謝 本文的寫作參考了中國LINUX論壇的網友hoyt的文章,可以在gtkvb.cffd.org.cn找到,在此表示感謝。 參考資料 * 本文的所有代碼可以在這裡下載。 * Thomas Hunger寫的文章:Gobject tutorial * 中國 linux 論壇的網友 hoyt 的網頁 http://gtkvb.cosoft.org.cn/ 上的文章 * 與本文相關的另一篇文章《淺析GLib》   關於作者 宋國偉,鄉村小學英語教師,他是《GTK+2.0編程范例》(清華大學出版社出版)一書的作者,業余時間致力於用GTK+開發LINUX GUI應用程序,可以通過電子郵件地址 [email protected] 與他聯系。 返回前一頁 本站由張善友制作並維護 copyright 2002-2003 All rights reserved



Copyright © Linux教程網 All Rights Reserved