簡單,再簡單一些!

從雙向鏈表的設計開始』彷佛將問題搞的有些複雜了。可能由於它是 4 年前寫的,那時我喜歡將簡單的問題複雜化……這篇文章嘗試將 GObject 與 C++ 代碼作一次『映射』,藉助 C++ 來理解 GObject 的基本編程框架,而後藉助代碼生成器保護咱們的手指。ios

先觀察如下 C++ 代碼:c++

#include <iostream>

class MyObject {
public:
        MyObject() {std::cout << "對象初始化" << std::endl;}
};

int main() {
        MyObject my_obj;
        return 0;
}

只要具有一點 C++ 類的知識,上述 C++ 代碼應該不難理解。下面用 GObject 對其進行逐步模擬。編程

『類』的類型

如下 C++ 代碼segmentfault

class MyObject;

用 GObject 可表述爲:api

#include <glib-object.h>

typedef struct _MyObjectClass {
        GObjectClass parent_class;
} MyObjectClass;

也就是說,GObject 中的『類結構體』的做用是面向 GObject 類型系統聲明一個『類』的類型。bash

須要注意一點,C++ 中的類能夠不須要從其餘類派生,而 GObject 的『類結構體』一般須要從一個叫作 GObjectClass 的『類結構體』派生而成。框架

類的定義

如下 C++ 代碼編程語言

class MyObject {
};

用 GObject 可表述爲:函數

#include <glib-object.h>

typedef struct _MyObject{
        GObject parent_instance;
} MyObject;
 
typedef struct _MyObjectClass {
        GObjectClass parent_class;
} MyObjectClass;

G_DEFINE_TYPE(MyObject, my_object, G_TYPE_OBJECT);

這就是說,GObject 中的『實例結構體』模擬的是 C++ 中的『類』類型的定義。ui

須要注意一點,C++ 中的類能夠不須要從其餘類派生,而 GObject 中的類,其『實例結構體』一般須要從一個叫作 GObject 的『實例結構體』派生而成。

動態類型系統

C++ 中的數據類型是靜態的,在代碼編譯階段由被編譯器肯定。儘管 C++ 提供了 RTTI(運行時類型識別),可是 RTTI 依然是 C++ 編譯器實現的。

Python、Ruby、Lua 之類的動態語言,它們的數據類型是由解釋器肯定的。

GObject 中的數據類型是動態的,在程序運行階段由 GObject 類型系統肯定。若是對上一節的 GObject 代碼仔細推敲,不難發現其數據類型是動態的,即『類』的類型是在程序運行時在 GObject 類型系統中註冊的。

看下面的代碼:

G_DEFINE_TYPE(MyObject, my_object, G_TYPE_OBJECT);

若是使用

$ gcc $(pkg-config --cflags gobject-2.0) -E your-source.c

那麼即可將 G_DEFINE_TYPE 展開爲下面的 C 代碼:

static void my_object_init(MyObject * self);
static void my_object_class_init(MyObjectClass * klass);
static gpointer my_object_parent_class = ((void *) 0);
static gint MyObject_private_offset;
static void
my_object_class_intern_init(gpointer klass)
{
    my_object_parent_class = g_type_class_peek_parent(klass);
    if (MyObject_private_offset != 0)
        g_type_class_adjust_private_offset(klass, &MyObject_private_offset);
    my_object_class_init((MyObjectClass *) klass);
}

__attribute__ ((__unused__))
static inline gpointer
my_object_get_instance_private(const MyObject * self)
{
    return (((gpointer) ((guint8 *) (self) + (glong) (MyObject_private_offset))));
}

GType
my_object_get_type(void)
{
    static volatile gsize g_define_type_id__volatile = 0;
    if (g_once_init_enter(&g_define_type_id__volatile)) {
                GType g_define_type_id = g_type_register_static_simple(((GType) ((20) << (2))),
                                                                       g_intern_static_string("MyObject"),
                                                                       sizeof(MyObjectClass),
                                                                       (GClassInitFunc) my_object_class_intern_init,
                                                                       sizeof(MyObject),
                                                                       (GInstanceInitFunc) my_object_init,
                                                                       (GTypeFlags) 0);
        }
        return g_define_type_id__volatile;
};

上述代碼中的 my_object_get_type 函數的定義被我簡化了一下,只保留其大意,主要是由於實際的代碼難以卒讀。

GObject 類型系統之因此可以接受 MyObject 這個『類』的類型,徹底拜 my_object_get_type 函數所賜。由於 my_object_get_type 函數調用了 g_type_register_static_simple 函數,後者由 GObject 類型系統提供,其主要職責就是爲 GObject 類型系統擴充人馬。my_object_get_typeg_type_register_static_simple 彙報:『我手裏有個 MyObject 類,它由 GObject 類派生,它的姓名、三圍、籍貫、民族分別爲 ……@#$%^&*(……blab……blab……』,而後 g_type_register_static_simple 就爲 MyObject 登記造冊,今後 GObject 類型系統中就有了 MyObject 這號人物了。

my_object_get_type 函數的定義利用了 static 變量實現瞭如下功能:

  1. my_object_get_type 第一次被調用時,會向 GObject 類型系統註冊 MyObject 類型,而後對 MyOject 類進行實例化,產生對象,最後返回對象的數據類型的 ID;

  2. my_object_get_type 第二次被調用開始,它就只進行 MyOject 類的實例化,不會再重複向 GObject 類型系統註冊類型。

那麼 my_object_get_type 會被誰調用?它會被 g_object_new 調用,全部 GObject 類派生的類型,皆可由 g_object_new 函數進行實例化,例如:

MyObject *my_obj = g_object_new(my_object_get_type(), NULL);

死啦死啦說,不拉屎會憋死咱們,不吃飯活七八天,不喝水活五六天,不睡覺活四五天,雜事養咱們也要咱們的命……因此 G_DEFINE_TYPE 宏就出現了,它悄悄的執行着這些雜事……因此,C++ 們就出現了,class 們悄悄的執行着這些雜事……

構造函數

除了向 GObject 類型註冊新類型的相關信息,G_DEFINE_TYPE 宏還爲咱們聲明瞭『類』的類型與『類』的實例的初始化函數:

static void my_object_init(MyObject * self);
static void my_object_class_init(MyObjectClass * klass);

my_object_class_init 是『類』的類型初始化函數,它的做用是使得用戶可以在『類』的類型初始化階段插入一些本身須要的功能。這個函數是任何一種支持面向對象的編程語言都不須要的,由於這些語言的編譯器(解釋器)認爲用戶沒有必要在『類』這種類型的初始化階段執行本身的一些任務。可是 GObject 的類型管理系統須要這個東西,由於在這個階段,用戶能夠『類』的類結構體(例如 MyObjectClas)中的數據進行一些符合本身需求的修改。請記住這一點,由於很快就能夠看到咱們有必要去修改『類』的類結構體。

my_object_init 是『類』的實例的初始化函數,能夠將它理解爲 C++ 對象的構造函數。

如下 C+ 代碼,

#include <iostream>

class MyObject {
public:
        MyObject() {std::cout << "對象初始化" << std::endl;}
};

用 GObject 可描述爲:

#include <stdio.h>
#include <glib-object.h>

typedef struct _MyObject{
        GObject parent_instance;
} MyObject;
 
typedef struct _MyObjectClass {
        GObjectClass parent_class;
} MyObjectClass;

G_DEFINE_TYPE(MyObject, my_object, G_TYPE_OBJECT);

static void my_object_class_init(MyObjectClass * klass) {
}

static void my_object_init(MyObject * self) {
        printf("對象初始化\n");
}

C++ 的對象除了有構造函數,還有析構函數。GObject 也有析構函數,可是這篇文章先放它一馬,由於這是一個很是有趣的主題,最好是爲它單獨寫一篇文章。

運行時類型識別

咱們的程序已經在 GObject 數據類型系統中創造出來 MyObject 類型,那麼用這種類型就能夠產生無數個實例,它們就是類型爲 MyObject 的對象。一個程序在運行時創造了一個 MyObject 類型的對象,那麼這個程序知道這個對象的類型是 MyObject 類型,知道它所屬的 MyObject 類型是派生自 GObject 類型麼?這就是所謂的『RTTI(運行時類型識別)』,有的語言則將其稱爲『自省』。

事實上如今並無任何一個程序可以具有自省能力,咱們人類自身也沒法回答『我是誰』這些的哲學問題。『我是誰』此類問題的答案,只有創造『我』的傢伙才知道。

GObject 類型系統創造了『類』的類型,而後由『類』的類型創造出對象。所以,它知道任何一個它由所創造的對象的類型。例如,對於任何由 GObject 類派生出來的類,它的對象可以使得如下條件成立:

MyObject *my_obj = g_object_new(MY_TYPE_OBJECT, NULL);

if (G_IS_OBJECT(my_obj)) {
        printf("My type is GObject!\n");
}

也能夠檢查 my_obj 的類型是否爲 MyObject 類:

if (G_TYPE_CHECK_INSTANCE_TYPE(my_obj, my_object_get_type())) {
        printf("My type is MyObject!\n");
}

爲了方便,能夠爲 MyObject 類定義一個與 G_IS_OBJECT 類似的宏:

#define MY_IS_OBJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE(my_obj, my_object_get_type()))

而後,能夠像下面這樣檢查 my_obj 的類型:

if (MY_IS_OBJECT(my_obj)) {
        printf("My type is MyObject!\n");
}

代碼生成器

雖然 GObject 的代碼看上去較爲繁瑣,可是這些代碼具備必定的模式。爲了節省字符輸入,能夠考慮爲它寫一個代碼生成器。

我在 Emacs 裏寫了一個簡單的生成器:

(defun make-gobject-h (project class parent)
  "Insert some macroes about gobject"
  (interactive "sWhat's the project name? \nsWhat's the class name? \nsWho's the parent of this class? ")
  (let ((P (upcase project)) 
    (O (upcase class))
    (p (downcase project))
    (o (downcase class))
    (pp (capitalize project))
    (oo (capitalize class)))
    (insert (format "#ifndef %s_%s_HEAD\n" P O))
    (insert (format "#define %s_%s_HEAD\n\n" P O))
    (insert (format "#include <glib-object.h>\n\n"))
    (insert (format "#define %s_TYPE_%s (%s_%s_get_type ())\n" P O p o))
    (insert (format "#define %s_%s(object) (G_TYPE_CHECK_INSTANCE_CAST((object), %s_TYPE_%s, %s%s))\n" P O P O pp oo))
    (insert (format "#define %s_%s_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), %s_TYPE_%s, %s%sClass))\n" P O P O pp oo))
    (insert (format "#define %s_IS_%s(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), %s_TYPE_%s))\n" P O P O))
    (insert (format "#define %s_IS_%s_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), %s_TYPE_%s))\n" P O P O))
    (insert (format "#define %s_%s_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), %s_TYPE_%s, %s%sClass))\n\n" P O P O pp oo))
    (insert (format "typedef struct _%s%s %s%s;\n" pp oo pp oo))
    (insert (format "typedef struct _%s%s {\n" pp oo))
    (insert (format "\t%s parent_instance;\n" parent))
    (insert (format "};\n"))
    (insert (format "typedef struct _%s%sClass %s%sClass;\n" pp oo pp oo))
    (insert (format "typedef struct _%s%sClass {\n" pp oo))
    (insert (format "\t%sClass parent_class;\n" parent))
    (insert (format "};\n\n"))
    (insert (format "GType %s_%s_get_type(void);\n\n" p o ))
    (insert (format "#endif\n"))))

這個生成器,在 Emacs 裏的用法是執行 M-x make-gobject-h 命令,而後像下面這樣回答三個問題:

Q:What's the project name?
A:My

Q:What's the class name?
A:Object

Q:Who's the parent of this class?
A:GObject

而後便可在 Emacs 當前緩衝區中生成如下代碼:

#ifndef MY_OBJECT_HEAD
#define MY_OBJECT_HEAD

#include <glib-object.h>

#define MY_TYPE_OBJECT (my_object_get_type ())
#define MY_OBJECT(object) (G_TYPE_CHECK_INSTANCE_CAST((object), MY_TYPE_OBJECT, MyObject))
#define MY_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), MY_TYPE_OBJECT, MyObjectClass))
#define MY_IS_OBJECT(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), MY_TYPE_OBJECT))
#define MY_IS_OBJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), MY_TYPE_OBJECT))
#define MY_OBJECT_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS((object), MY_TYPE_OBJECT, MyObjectClass))

typedef struct _MyObject MyObject;
typedef struct _MyObject {
    GObject parent_instance;
};
typedef struct _MyObjectClass MyObjectClass;
typedef struct _MyObjectClass {
    GObjectClass parent_class;
};

GType my_object_get_type(void);

#endif

上述代碼,能夠保存爲 my-object.h 文件,而後再新建一份 my-object.c 文件,在其中放置如下內容:

#include "my-object.h"
 
G_DEFINE_TYPE(MyObject, my_object, G_TYPE_OBJECT);
 
static void
my_object_init(MyObject *self)
{
        printf("對象初始化\n");
}
 
static void
my_object_class_init(MyObjectClass *klass)
{
}

這樣就完成了一個完整的 GObject 版本 MyObject 類的定義。

my-object.c 中的代碼,也能夠寫一個代碼生成器來生成:

(defun make-gobject-c (project class parent-type)
  "Insert some macroes about gobject"
  (interactive "sWhat's the project name? \nsWhat's the class name? \nsWhat's the parent type of this class? ")
  (let ((p (downcase project))
    (o (downcase class))
    (pp (capitalize project))
    (oo (capitalize class)))
    (insert (format "#include \"%s-%s.h\"\n\n" p o))
    (insert (format "G_DEFINE_TYPE(%s%s, %s_%s, %s)\n\n" pp oo p o parent-type))
    (insert (format "static void\n"))
    (insert (format "%s_%s_init(%s%s *self)\n{\n\n}\n\n" p o pp oo))
    (insert (format "static void\n"))
    (insert (format "%s_%s_class_init(%s%sClass *klass)\n{\n\n}\n\n" p o pp oo))))

在 Emacs 裏執行 M-x make-gobject-c 命令,而後像下面這樣回答三個問題:

Q:What's the project name?
A:My

Q:What's the class name?
A:Object

Q:What's the parent type of this class?
A:G_TYPE_OBJECT

而後便可生成上述的 my-object.c 文件中的主要內容。

總結

GObject 的數據類型系統最大的特色是數據類型的動態性,雖然是不得已而爲之,可是卻別有一番情趣。基於這種數據類型系統,咱們可以在程序的運行過程當中創造新的數據類型,可以由一種數據類型派生出新的數據類型,還可以識別對象的類型。

雖然 GObject 的這一切看上去很是繁瑣,可是經過代碼生成器就能夠明白,若是遵照一些約定,繁瑣的代碼能夠基於 4 個參數生成。

GObject 的類型系統既然是動態的,它支持程序在運行中生成數據類型,也支持程序在運行中銷燬數據類型。有關數據類型的銷燬,這個功能比較偏僻,通常狀況下用不着,若對這個主題感興趣,可參考:http://www.gonwan.com/?p=50

相關文章
相關標籤/搜索