GObject功能強大,相對的其學習曲線也頗為陡峭,能夠找到的文件不多。官方網站有提供參考文件,個人覺得僅適用於對系統有一定程度瞭解的人,其導覽教學文件,並無提供完整的原始碼,僅憑些許程式碼片段(source code snippets)很難快速入手及測試。雖然可以透過glib、gtk+的原始碼研究參考,也是枝節頗多,增添困擾。
在這一系列的文章中,我將分享GObject system的使用方法,還有簡易的測試原始碼。
本文範例簡介
在本文中,我將建立一個叫Shape之類別。Shape類別將擁有一個方法calculateArea()用來計算面積,由於Shape為抽象類別(abstraction class),並不實作calculateArea()。之後的文章,我將加入Circle及Rectangle,他們將繼承自Shape並擁有各自的面積計算方法。
宣告類別
類別的宣告在GObject system裡有特殊的規範,這是由於C programming language在語法(syntax)中並無支援OO,只能從語意(semantics)上支援。在GObject中建立類別,需要同時宣告該類別的描述資料(metadata)以及屬於該類別的資料,GObject system會在執行時期將兩者結合。
在實作中要如何達到上述的結果,可由下列原始碼以較直觀的方式來說明。
Shape類別之宣告:
有了上面的宣告後,Shape尚無法運作,因為GObject需要有更多的資料才能將產生Shape物件。而這些資料都需要實作者提供。首先,我們需要向GObject system註冊Shape型別。型別註冊後會取得一個型別代號,透過此型別代號於執行時期才可以建立物件。型別的註冊方式如下:
Shape類別公開函式(public function)之呼叫:
在Shape類別裡面我們定義了一個虛擬公開函式(virtual public function)叫calculateArea,所有從屬於Shape(包含棋子類別)的物件,都可透過此方法計算面積。實作上,我們可以定義一個介面,使用者可透過此介面呼叫虛擬函式(virtual function),達成資料封裝的效果。
GObject之類別宣告需要許多冗長的原始碼,初時容易令人望之卻步,但是仔細觀察可發現其實都有脈絡可循。也由於宣告方式大同小異,只要剪剪貼貼就可達成目的,反而容易因不之其所以然,導致許多的後遺症。瞭解概念後,剪貼也會比較有效率(大誤),未來若要除錯(debug)也會簡單許多。
本文範例原始碼
本文範例簡介
在本文中,我將建立一個叫Shape之類別。Shape類別將擁有一個方法calculateArea()用來計算面積,由於Shape為抽象類別(abstraction class),並不實作calculateArea()。之後的文章,我將加入Circle及Rectangle,他們將繼承自Shape並擁有各自的面積計算方法。
figure-01: Class diagram of the simple shape system |
類別的宣告在GObject system裡有特殊的規範,這是由於C programming language在語法(syntax)中並無支援OO,只能從語意(semantics)上支援。在GObject中建立類別,需要同時宣告該類別的描述資料(metadata)以及屬於該類別的資料,GObject system會在執行時期將兩者結合。
figure-02: Composition of a typical object of GObject |
Shape類別之宣告:
typedef struct _Shape Shape; typedef struct _ShapeClass ShapeClass; struct _Shape { GObject parent; }; struct _ShapeClass { GObjectClass parent; guint (*calculateArea)(Shape* shape); };struct _Shape裡面宣告的是Shape物件的資料內容,也就是上圖所說的instance data,因為Shape並無父類別(parent class),所以在此需要將它的父類別宣告為GObject,而在Shape物件的資料宣告中也要將其放入。注意,不管是metadata或是instance data的宣告,都要將其相對的parent宣告擺在第一個位置。Shape的公共函式(public function)宣告於ShapeClass中。順帶一提的是,可以將struct _Shape定義為Shape,還有把struct _ShapeClass定義為ShapeClass,此乃GObject的命名慣例。
有了上面的宣告後,Shape尚無法運作,因為GObject需要有更多的資料才能將產生Shape物件。而這些資料都需要實作者提供。首先,我們需要向GObject system註冊Shape型別。型別註冊後會取得一個型別代號,透過此型別代號於執行時期才可以建立物件。型別的註冊方式如下:
#define SHAPE_TYPE (shape_get_type()) typedef struct _Shape Shape; typedef struct _ShapeClass ShapeClass; struct _Shape { GObject parent; }; struct _ShapeClass { GObjectClass parent; guint (*calculateArea)(Shape* shape); }; GType shape_get_type();
static void shape_base_init(ShapeClass* self); static void shape_base_finalize(ShapeClass* self); static void shape_class_init(ShapeClass* self); GType shape_get_type() { static GType type = 0; if (type == 0) { static const GTypeInfo info = { sizeof(ShapeClass), (GBaseInitFunc)shape_base_init, (GBaseFinalizeFunc)shape_base_finalize, (GClassInitFunc)shape_class_init, NULL, NULL, sizeof(Shape), 0, NULL, NULL }; type = g_type_register_static(G_TYPE_OBJECT, "Shape", &info, 0); } return type; } static void shape_base_init(ShapeClass* self) { g_print("Shape(0x%08x)::base::initialize\n", (guint)self); } static void shape_base_finalize(ShapeClass* self) { g_print("Shape(0x%08x)::base::finalize\n", (guint)self); } static void shape_class_init(ShapeClass* klass) { g_print("Shape(0x%08x)::class::initialize\n", (guint)klass); klass->calculateArea = NULL; }我們可以發現光要註冊型別就讓原始碼爆增不少,不過仔細觀察可以發現這些原始碼主要是圍繞著g_type_register_static,透過g_type_register_static可向GObject註冊取得型別代碼。之後,就可以由g_object_new產生隸屬該型別之物件。其中,由實作者提供之shape_base_init、shape_base_finalize、shape_class_init乃是供GObject system於管理物件生命週期時使用。使用法將於下篇文章探討。
Shape類別公開函式(public function)之呼叫:
在Shape類別裡面我們定義了一個虛擬公開函式(virtual public function)叫calculateArea,所有從屬於Shape(包含棋子類別)的物件,都可透過此方法計算面積。實作上,我們可以定義一個介面,使用者可透過此介面呼叫虛擬函式(virtual function),達成資料封裝的效果。
#define IS_SHAPE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SHAPE_TYPE)) #define SHAPE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SHAPE_TYPE, ShapeClass)) struct _Shape { GObject parent; }; struct _ShapeClass { GObjectClass parent; guint (*calculateArea)(Shape* shape); }; GType shape_get_type(); guint shape_calculate_area(Shape* shape);
... guint shape_calculate_area(Shape* shape) { if (!IS_SHAPE(shape)) return 0; if (SHAPE_GET_CLASS(shape)->calculateArea == NULL) { g_print("Shape::calculateArea is not implemented\n"); return 0; } return SHAPE_GET_CLASS(shape)->calculateArea(shape); } ...由於,calculateArea()是虛擬函數,在此GObject system透過函式指標的方式,達成子類別覆寫(override)的效果。shape_calculate_area()提供一個封裝過的介面,隱藏許多實作上的細節,這也是GObject systeme官方建議的作法。
GObject之類別宣告需要許多冗長的原始碼,初時容易令人望之卻步,但是仔細觀察可發現其實都有脈絡可循。也由於宣告方式大同小異,只要剪剪貼貼就可達成目的,反而容易因不之其所以然,導致許多的後遺症。瞭解概念後,剪貼也會比較有效率(大誤),未來若要除錯(debug)也會簡單許多。
本文範例原始碼
No comments:
Post a Comment