GObject學習筆記(二)型別建立與註冊

paw5zx發表於2024-12-09

前言

本文可在https://paw5zx.github.io/GObject-tutorial-beginner-02/中閱讀,體驗更加

上一節中我們介紹了GObject型別的類和例項變數的建立和使用。GObject是一個基本的可例項化類型別,是所有使用GObject系統的型別的基類,提供了繼承、封裝、多型等物件導向的核心特性。不過我們一般不直接使用GObject本身,而是透過繼承GObject來建立新的型別。

{% notel blue fa-circle-exclamation 注意:%}
對於GObject官方文件中:

可例項化的類型別:object(Instantiatable classed types: objects),本文以及後文中稱其為型別。
不可例項化的類型別:interface(Non-instantiatable classed types: interfaces),本文以及後文中稱其為介面。

本文中我們主要介紹一下GObject型別系統中的命名約定,以及介紹如何建立一個自定義的型別。

命名約定

{% note blue fa-circle-exclamation %}

命名約定貫穿整個GObject型別系統。

{% endnote %}

首先,我們先了解基本的命名約定:一個物件名由名稱空間(也可稱為moudle)和名稱組成。例如,GObject由名稱空間“G”和名稱“Object”組成。GtkWidget由名稱空間“Gtk”和名稱“Widget”組成。在本文中,為了更好的演示,我們將定義一個新型別,其名稱空間為“Paw”,名稱為“Double”,用於表示一個浮點資料。

好了,你現在已瞭解了基本的命名約定,下面我們來看一下更多的命名約定:

  • 型別名稱必須至少有三個字元長,並以a-z, A-Z或_開頭。
  • 函式名使用object_method模式:要在型別為Double的例項上定義名為add的函式,則函式名為double_save
  • 使用字首可以避免與其他專案的名稱空間衝突。如我的庫(或應用程式)名為Paw,就在所有函式名前加上paw_。例如:paw_double_add。字首應該是一個詞,即在第一個字母之後不應該包含任何大寫字母。例如,應該是Exampleprefix而不是ExamplePrefix。

除了上述約定,本文還將介紹其他命名約定,將在文中適時描述。

型別建立和註冊

型別的建立是指定義一個新的GObject型別,包括其所有的資料和行為。這會涉及到:

  • 定義類結構體和例項結構體
  • 定義型別相關的函式:如類初始化(class_init)、例項初始化(instance_init)等函式。

型別的註冊是將建立的新GObject型別在GObject型別系統中註冊,使其成為GObject系統可識別和使用的一部分。這會涉及到:

  • 呼叫型別註冊函式:g_type_register_staticg_type_register_dynamic,這些函式負責在GObject型別系統中註冊新建立的型別

在我們平時的使用中,型別的建立和註冊通常可以透過GObject提供的一些便利宏來完成,如G_DECLARE_FINAL_TYPEG_DEFINE_TYPE等。這些便利宏可以讓使用者不用關心一些型別建立和註冊的具體細節。

在本文中,為了更好地理解GObject型別系統,會先展示不使用便利宏的型別建立和註冊過程,之後再介紹便利宏。

{% notel blue fa-circle-exclamation 注意:%}
GObject型別系統中有兩種型別:靜態型別和動態型別。

對於靜態型別,即使所有例項都被銷燬後也不會銷燬其類。對於動態型別,在最後一個例項被銷燬時銷燬其類。GObject的型別是靜態的,其派生的型別也是靜態的。

手動

定義型別的類和例項結構體

類和例項結構體命名約定如下:

  • 類結構體的命名為:<Moudle><Name>Class,如PawDoubleClass。
  • 例項結構體的命名為:<Moudle><Name>,如PawDouble。
//類結構體
typedef struct _PawDoubleClass PawDoubleClass;
struct _PawDoubleClass
{
  GObjectClass parent_class;
};

PawDoubleClass的第一個成員必須是其父型別的類結構體

//例項結構體
typedef struct _PawDouble PawDouble;
struct _PawDouble
{
  GObject parent;
  double value;
};

PawDouble的第一個成員必須是其父型別的例項結構體。
PawDouble有自己的成員value,是PawDouble型別代表的浮點資料的值

定義初始化函式

類和例項的初始化函式命名約定如下:

  • 類初始化函式的命名為:<moudle>_<name>_class_init,如paw_double_class_init。
  • 例項初始化函式的命名為:<moudle>_<name>_init,如paw_double_init。
//類建構函式
static void paw_double_class_init(PawDoubleClass* class)
{
}

//例項建構函式
static void paw_double_init(PawDouble* self)
{
}

型別註冊

對於靜態型別,我們使用g_type_register_static將其註冊至GObject的型別系統中

//file: gtype.h
GType
g_type_register_static (GType            parent_type,
                        const gchar*     type_name,
                        const GTypeInfo* info,
                        GTypeFlags       flags);

其中:

  • parent_type:父類型別
  • type_name:型別名稱,如PawDouble
  • info:向型別系統傳遞型別初始化和銷燬的相關資訊。GTypeInfo結構體將在下文中介紹
  • flags:如果型別是抽象型別或抽象值型別,則設定它們的標誌位。否則,將其設為0。

GTyepInfo結構體:

typedef struct _GTypeInfo  GTypeInfo;

struct _GTypeInfo
{
  /* interface types, classed types, instantiated types */
  guint16                class_size;

  GBaseInitFunc          base_init;
  GBaseFinalizeFunc      base_finalize;

  /* interface types, classed types, instantiated types */
  GClassInitFunc         class_init;
  GClassFinalizeFunc     class_finalize;
  gconstpointer          class_data;

  /* instantiated types */
  guint16                instance_size;
  guint16                n_preallocs;
  GInstanceInitFunc      instance_init;

  /* value handling */
  const GTypeValueTable  *value_table;
};

此結構必須在型別註冊前建立,其中:

  • class_size: 類的大小。例如,PawDouble型別的類大小為sizeof(PawDoubleClass)
  • base_init, base_finalize: 這些函式初始化/銷燬類的動態成員。在許多情況下,它們不是必需的,被賦值為NULL。詳見GObject.BaseInitFuncGObject.ClassInitFunc
  • class_init: 類的初始化函式。使用者需要將定義的類初始化函式賦值給class_init成員。按照命名約定,類初始化函式名稱為<moudle>_<name>_class_init,例如paw_double_class_init
  • class_finalize: 類的銷燬函式。因為GObject的子類型別是靜態的,所以它沒有銷燬函式。將class_finalize成員賦值為NULL即可。
  • class_data: 使用者提供資料,傳遞給類的初始化/銷燬函式。通常賦值為 NULL。
  • instance_size: 例項的大小。例如,PawDouble型別的例項大小為sizeof(PawDouble)
  • n_preallocs: 用於指定預分配的例項數量。自GLib 2.10以來,該欄位被忽略。
  • instance_init: 例項的初始化函式。使用者需要將定義的例項初始化函式賦值給instance_init成員。按照命名約定,例項初始化函式名稱為<moudle>_<name>_init,例如 paw_double_init
  • value_table: 這通常只對基本型別有用。如果型別是GObject的後代,賦值為NULL。

我們將編寫一個函式paw_double_get_type,用於向GObject型別系統註冊新型別,並返回所註冊型別的id

GType paw_double_get_type(void) 
{
    static GType type = 0;
    GTypeInfo info;

    if(type == 0) 
    {
        //賦值
        info.class_size = sizeof(PawDoubleClass);
        info.base_init = NULL;
        info.base_finalize = NULL;
        info.class_init = (GClassInitFunc) paw_double_class_init;
        info.class_finalize = NULL;
        info.class_data = NULL;
        info.instance_size = sizeof(PawDouble);
        info.n_preallocs = 0;
        info.instance_init = (GInstanceInitFunc) paw_double_init;
        info.value_table = NULL;
        //註冊型別
        type = g_type_register_static(G_TYPE_OBJECT, "PawDouble", &info, 0);
    }
    return type;
}

彙總

上述的型別建立和註冊過程彙總如下:

#include <glib-object.h>

#define PAW_TYPE_DOUBLE  (paw_double_get_type())

//定義例項結構體
typedef struct _PawDouble PawDouble;
struct _PawDouble
{
  GObject parent;
  double value;
};

//定義類結構體
typedef struct _PawDoubleClass PawDoubleClass;
struct _PawDoubleClass
{
  GObjectClass parent_class;
};

//類建構函式
static void paw_double_class_init(PawDoubleClass* class)
{
}

//例項建構函式
static void paw_double_init(PawDouble* self)
{
}

//首次呼叫時註冊型別
//返回型別
GType paw_double_get_type(void) 
{
    static GType type = 0;
    GTypeInfo info;

    if(type == 0) 
    {
        info.class_size = sizeof(PawDoubleClass);
        info.base_init = NULL;
        info.base_finalize = NULL;
        info.class_init = (GClassInitFunc) paw_double_class_init;
        info.class_finalize = NULL;
        info.class_data = NULL;
        info.instance_size = sizeof(PawDouble);
        info.n_preallocs = 0;
        info.instance_init = (GInstanceInitFunc) paw_double_init;
        info.value_table = NULL;
        type = g_type_register_static(G_TYPE_OBJECT, "PawDouble", &info, 0);
    }
    return type;
}

int main(int argc, char **argv)
{
    GType dtype;
    PawDouble* d;

    dtype = paw_double_get_type(); /* or dtype = PAW_TYPE_DOUBLE */
    if(dtype)
        g_print("Registration was a success. The type is %lx.\n", dtype);
    else
        g_print("Registration failed.\n");

    d = g_object_new(PAW_TYPE_DOUBLE, NULL);
    if(d)
        g_print("Instantiation was a success. The instance address is %p.\n", d);
    else
        g_print("Instantiation failed.\n");
    g_object_unref(d); /* Releases the object d. */

  return 0;
} 
  • 20-28:類初始化函式和例項初始化函式,引數class是指類結構體,引數self是指例項結構體。這兩個函式在示例中內容為空,但他們是註冊過程必須的。

  • 30-52:paw_double_get_type函式,返回PawDouble型別的id。這種函式的命名總是遵循<moudle>_<name>_get_type。同時,宏<MOUDLE>_TYPE_<NAME>(所有字元都是大寫)是這個函式的別名。paw_double_get_type函式有一個靜態變數type來儲存物件的型別。在此函式的第一次呼叫時,type是零。然後函式內呼叫 g_type_register_static將新型別註冊到型別系統中。在第二次和之後的呼叫中,該函式只是返回type,因為靜態變數type已經被g_type_register_static賦予了非零值,並且它保持該值。

  • 39-49:設定info結構體並呼叫g_type_register_static註冊。

  • 54-73:主函式。獲取PawDouble型別id並顯示它。函式g_object_new被用來建立型別例項。GObject API文件中表示g_object_new返回一個指向GObject例項的指標,但實際上它返回的是一個gpointergpointer基本等同於void*,可以被賦值給指向任何型別的指標。因此,語句d = g_object_new(PAW_TYPE_DOUBLE, NULL);是正確的。如果函式g_object_new返回的是GObject*,那麼就需要對返回的指標進行型別轉換。在建立完成後,我們列印了例項的地址。最後,使用函式g_object_unref釋放並銷燬例項。

剩餘內容請前往https://paw5zx.github.io/GObject-tutorial-beginner-02/中閱讀。

相關文章