前言
本文可在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_static
或g_type_register_dynamic
,這些函式負責在GObject型別系統中註冊新建立的型別
在我們平時的使用中,型別的建立和註冊通常可以透過GObject提供的一些便利宏來完成,如G_DECLARE_FINAL_TYPE
和G_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.BaseInitFunc和GObject.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例項的指標,但實際上它返回的是一個gpointer
。gpointer
基本等同於void*
,可以被賦值給指向任何型別的指標。因此,語句d = g_object_new(PAW_TYPE_DOUBLE, NULL);
是正確的。如果函式g_object_new
返回的是GObject*
,那麼就需要對返回的指標進行型別轉換。在建立完成後,我們列印了例項的地址。最後,使用函式g_object_unref
釋放並銷燬例項。
剩餘內容請前往https://paw5zx.github.io/GObject-tutorial-beginner-02/中閱讀。