6/04/2011

GObject 入門 (Part 1) - 宣告類別

GObject功能強大,相對的其學習曲線也頗為陡峭,能夠找到的文件不多。官方網站有提供參考文件,個人覺得僅適用於對系統有一定程度瞭解的人,其導覽教學文件,並無提供完整的原始碼,僅憑些許程式碼片段(source code snippets)很難快速入手及測試。雖然可以透過glib、gtk+的原始碼研究參考,也是枝節頗多,增添困擾。

在這一系列的文章中,我將分享GObject system的使用方法,還有簡易的測試原始碼。

本文範例簡介
在本文中,我將建立一個叫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