GObject
GObject庫是Glib庫的動態類型系統實現,它實現了:
- 基於引用計數的內存管理
- 實例的構造和析構
- 通用的set/get的屬性獲取方法
- 簡單易用的信號機制
對象實例化
所述g_object_new
的功能家族可用於實例化從GObject的基類型繼承的任何的GType。所有這些函數都確保類和實例結構已經被GLib的類型系統正確地初始化,然后在一個或另一個地方調用用於的構造函數類方法:
- 調用
g_type_create_instance
分配並清空內存 - 根據構造參數初始化對象實例
雖然人們可以期望所有的類和實例成員(除了指向父母的字段)被設置為零,但是有些人認為明確地設置它們是一個好習慣。一旦所有施工操作完成並且構造器屬性設置完畢,就調用構造的類方法。從GObject繼承的對象被允許覆蓋這個構造的類方法。以下示例顯示了ViewerFile如何覆蓋父項目的構建過程:
#define VIEWER_TYPE_FILE viewer_file_get_type ()
G_DECLARE_FINAL_TYPE(ViewerFile, viewer_file, VIEWER, FILE, GObject)
struct _ViewerFile
{
GObject parent_instance ;
/ *實例成員* /
};
/ *將創建viewer_file_get_type並設置viewer_file_parent_class * /
G_DEFINE_TYPE(ViewerFile, viewer_file, G_TYPE_OBJECT)
static void
viewer_file_constructed (GObject * obj)
{
/ *根據構造函數屬性更新對象狀態* /
/ *始終鏈接到父構造函數以完成對象
初始化。* /
G_OBJECT_CLASS(viewer_file_parent_class)->constructed(obj);
}
static void
viewer_file_class_init (ViewerFileClass * klass)
{
GObjectClass * object_class = G_OBJECT_CLASS(klass);
object_class->constructed = viewer_file_constructed;
}
static void
viewer_file_init (ViewerFile * self)
{
/ *初始化對象* /
}
如果用戶用下面的方式實例化一個對象ViewerFile:
ViewerFile *file = g_object_new (VIEWER_TYPE_FILE, NULL);
如果這是這樣一個對象的第一個實例化,那么 viewer_file_class_init
函數將在任何viewer_file_base_class_init
函數之后被調用。這將確保這個新對象的類結構被正確初始化,在這里viewer_file_class_init
預計會覆蓋GObject
的類方法並設置類自己的方法。在上面的例子中,構造函數方法是唯一被覆蓋的方法:它被設置為 viewer_file_constructor
。
一旦g_object_new
獲得了一個初始化類結構的引用,它就調用它的構造函數方法來創建新對象的一個實例,如果構造函數已被覆蓋viewer_file_class_init
,重寫的構造函數必須鏈接到父項的構造函數,為了找到父類和鏈父類的構造函數,我們可以使用宏viewer_file_parent_class
為我們設置的指針G_DEFINE_TYPE
。
最后由鏈中最后一個構造函數調用g_object_constructor
。這個函數通過g_type_create_instance
分配對象的實例緩沖區,這時候如果注冊了instance_init
函數,將會被調用。在instance_init
返回后,對象完成初始化,並允許用戶調用其方法。當 g_type_create_instance
返回時,g_object_constructor
將設置構造屬性(執行g_object_new時傳入的參數),並返回到用戶的構造函數。
上面描述的過程可能看起來有點復雜,但是可以通過下面的表格容易地總結,其中列出了調用的函數g_object_new
及其調用順序:
// 下面只是偽代碼,很多函數可能是都是調用鏈,例如g_object_constructor()等,這邊都只使用最后調用的g_object_XX函數來代替
g_object_new
{
// 注冊類信息
g_type_class_ref()
{
type_class_init_Wm()
{
g_object_base_class_init()
// 此處就是viewer_file_class_init
g_object_do_class_init()
}
}
g_object_new_internal()
{
// 構造函數被重載的情況
g_object_new_with_custom_constructor()
{
g_object_constructor()
{
g_type_create_instance()
{
// 此處就是viewer_file_init
instance_init()
}
}
// 此處就是viewer_file_constructed
g_object_constructed()
}
}
}
調用時間 | 函數調用 | 函數的參數 | 備注 |
---|---|---|---|
首次調用g_object_new創建目標類型 | 目標類型的base_class_init函數 | 從基類開始調用base_class_init,然后遞歸調用子類,直到到達目標類型。 | 從未在實踐中使用,你不太可能會需要它。 |
首次調用g_object_new創建目標類型 | 目標類型的class_init函數 | 目標類型的類結構 | 在這里您應該確保初始化或重寫類方法(即為每個類的方法分配其函數指針),並創建與對象關聯的信號和屬性。 |
首次調用g_object_new創建目標類型 | 接口的base_init函數 | 接口的vtable | - |
首次調用g_object_new創建目標類型 | 接口的interface_init函數 | 接口的vtable | - |
每次調用g_object_new創建目標類型 | 目標類型的類constructor方法:GObjectClass->constructor | 對象的實例 | 如果您需要以自定義的方式處理構造屬性或者實現一個單例類,請重寫構造方法,並確保在執行自己的初始化之前鏈接到對象的父類。如果存在疑問,則不要重寫構造方法。 |
每次調用g_object_new創建目標類型 | 目標類型的instance_init函數 | 從基類開始調用instance_init,然后遞歸調用子類,直到到達目標類型。 | 提供一個instance_init函數來初始化你的對象,在它的構造屬性被設置之前。這是初始化GObject實例的首選方法。這個函數相當於C++的構造函數。 |
每次調用g_object_new創建目標類型 | 目標類型的類constructed方法:GObjectClass->constructed | 對象的實例 | 如果在所有構造屬性設置完畢后需要執行對象初始化步驟。這是對象初始化過程的最后一步,只有當constructor方法返回一個新的對象實例(而不是現有的單例)時才被調用。 |
讀者應該關心函數調用順序的一點點轉變:從技術上講,類的構造函數方法是在 GType的instance_init 函數之前g_type_create_instance調用的(因為哪個調用instance_init是由g_object_constructor頂層類的構造方法調用的 ,哪些用戶需要連接到),用戶提供的構造函數中運行的代碼將始終在 GType的instance_init函數之后運行,因為在執行任何有用的操作之前,用戶提供的構造函數必須(您已經被警告)鏈接起來。
內存管理
引用計數
使用線程安全的g_object_ref()
/g_object_unref()
函數來增加和減少對象引用計數。調用g_object_new
后引用計數被初始化成1。當引用計數減為0時,g_object_unref
還將調用析構函數釋放對象。
調用時間 | 涉及函數 | 函數參數 | 備注 |
---|---|---|---|
目標類型實例最后一次調用g_object_unref |
目標類型的dispose 函數 |
GObject實例 | 當廢棄函數執行完成后,對象將不再擁有任何成員變量對象的引用(對象本身的內存未被釋放),雖然還能夠被使用(在析構函數被調用前),當然很可能會返回錯誤碼,但是不會拋出內存異常。廢棄函數可以被多次調用而不用擔心會拋出異常。廢棄函數在函數返回前要調用父類的廢棄函數實現,保持調用鏈的完整。 |
目標類型的finalize 函數 |
GObject實例 | 析構函數將完成廢棄函數的后續動作-釋放對象內存。析構函數只能夠被調用一次,並且同廢棄函數一樣,需要在函數返回前調用父類的析構函數實現。 | |
目標類型的最后一個實例最后一次調用g_object_unref |
接口的interface_finalize 函數 |
接口的虛表vtable | 不要在實踐中使用,除非有特殊需要 |
接口的base_finalize 函數 |
接口的虛表vtable | 不要在實踐中使用,除非有特殊需要 | |
接口的class_finalize 函數 |
目標類型的類結構 | 不要在實踐中使用,除非有特殊需要 | |
接口的base_finalize 函數 |
從基礎類型到目標類型的的繼承樹上的每個類結構,都調用一次base_finalize |
不要在實踐中使用,除非有特殊需要 |
弱引用
弱引用通常用來監視對象的析構,通過g_object_weak_ref
添加一個在對象析構時被調用的監控回調函數,這樣就可以在不調用g_object_ref
的情況下安全的保存一個對象的指針。
void g_object_weak_ref(GObject *object, // 需要建立弱引用的GObject對象
GWeakNotify notify, // 對象被釋放前需要調用的回調函數
gpointer data); // 傳遞給回調函數的參數
void (*GWeakNotify)(gpointer data, // 弱連接建立時傳入的數據,一般是希望保存對象指針的GObject對象
GObject *where_the_object_was); // 被析構的弱引用的GObject對象
消息系統
閉包
閉包是在GTK+和GNOME應用中用來表示回調函數的一種通用的抽象方法,閉包結構主要包括三個內容:
- 回調函數本身的函數指針,如下:
return_type function_callback (… , gpointer user_data);
- 傳遞給回調函數的用戶數據指針
user_data
- 閉包的析構函數
一個閉包會提供如下簡單的服務:
- 閉包的調用
g_closure_invoke
:對調用者隱藏回調函數調用細節 - 通知:閉包會通知監聽者某些事件,例如閉包的調用、失效以及終止。通過
g_closure_add_finalize_notifier
函數注冊監聽終止通知、g_closure_add_invalidate_notifier
函數注冊監聽失效通知,以及g_closure_add_marshal_guards
函數注冊監聽調用通知。通過g_closure_remove_finalize_notifier
和g_closure_remove_invalidate_notifier
函數可移除監聽。
C 語言閉包
如果想使用C或C++關聯回調函數到某個事件上,可使用GCClosures提供的最簡單的API函數g_signal_connect
。
g_cclosure_new
/g_cclosure_new_swap
將會創建一個調用用戶自定義回調函數的閉包,並且使用用戶提供的數據作為回調函數入參。閉包終止后使用destroy_data
函數進行析構。
Non-C 語言閉包
閉包隱藏了回調函數調用的細節。在C語言中,回調函數的調用就和函數調用很類似:就是為調用函數創建正確的堆棧,然后執行匯編指令。
C閉包方法將表示函數參數的GValues數組轉換成C類型的函數參數列表,然后調用用戶提供的C函數,獲取函數的返回值並將其轉換成GValue類型返回給方法調用者。下面就是一個簡單的閉包例子:
g_cclosure_marshal_VOID__INT (GClosure *closure,
GValue *return_value,
guint n_param_values,
const GValue *param_values,
gpointer invocation_hint,
gpointer marshal_data)
{
typedef void (*GMarshalFunc_VOID__INT) (gpointer data1,
gint arg_1,
gpointer data2);
register GMarshalFunc_VOID__INT callback;
register GCClosure *cc = (GCClosure*) closure;
register gpointer data1, data2;
g_return_if_fail (n_param_values == 2);
data1 = g_value_peek_pointer (param_values + 0);
data2 = closure->data;
callback = (GMarshalFunc_VOID__INT) (marshal_data ? marshal_data : cc->callback);
callback (data1,
g_marshal_value_peek_int (param_values + 1),
data2);
}
信號
GObject的信號和UNIX系統的信號沒有任何關系。
信號的注冊
我們通常使用g_signal_newv
、 g_signal_new_valist
和g_signal_new
來注冊信號:
guint g_signal_newv (const gchar *signal_name,
GType itype,
GSignalFlags signal_flags,
GClosure *class_closure,
GSignalAccumulator accumulator,
gpointer accu_data,
GSignalCMarshaller c_marshaller,
GType return_type,
guint n_params,
GType *param_types);
參數名 | 說明 |
---|---|
signal_name | 信號的唯一字符串標識 |
itype | 觸發信號的實例類型(g_signal_connect的第一個參數的類型) |
signal_flags | 定義關聯到信號上的閉包的調用順序 |
class_closure | 信號的默認閉包,如果它不為空,當信號被觸發時候它將被調用,調用的時間取決於signal_flags(g_signal_new中使用G_STRUCT_OFFSET宏獲取類成員函數作為默認閉包) |
accumulator | 一個函數指針,每個閉包被調用后都會執行此函數,如果函數返回FALSE,信號發射將停止,否則繼續。它通常可以用來統計與信號關聯的閉包的調用返回值。 |
accumulator_data | accumulator函數的入參 |
c_marshaller | 關聯到此信號的回調方法類型(方法的返回值和參數類型列表) |
return_type | 信號的返回類型 |
n_params | c_marshaller的參數個數 |
n_params | c_marshaller的參數類型列表 |
g_signal_new("session-display-removed", G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_HOOKS, G_STRUCT_OFFSET(VirtViewerSessionClass, session_display_removed),
NULL,
NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
VIRT_VIEWER_TYPE_DISPLAY);
signals[SPICE_MAIN_AGENT_GOT_REAL_RESOLUTION] =
g_signal_new("real-resolution",
G_OBJECT_CLASS_TYPE(gobject_class),
G_SIGNAL_RUN_LAST ,
0, // Pass 0 to not associate a class method slot with this signal.(如果關聯類方法,則設置為G_STRUCT_OFFSET(SpiceSessionClass, channel_new))
NULL, NULL,
g_cclosure_user_marshal_VOID__INT_INT_INT,
G_TYPE_NONE, //return type of handler
3, //the number of parameter types to follow
G_TYPE_INT, G_TYPE_INT, G_TYPE_INT // a list of types, one for each parameter
) ;
//============綁定類方法===============
struct _SpiceSessionClass
{
GObjectClass parent_class;
/* signals */
void (*channel_new)(SpiceSession *session, SpiceChannel *channel);
void (*channel_destroy)(SpiceSession *session, SpiceChannel *channel);
/*< private >*/
/*
* If adding fields to this struct, remove corresponding
* amount of padding to avoid changing overall struct size
*/
gchar _spice_reserved[SPICE_RESERVED_PADDING];
};
關聯信號
如果你想關聯一個閉包到信號上,你有三種選擇:
- 在信號注冊的時候注冊一個類閉包,這是一個系統范圍內的操作:類閉包在信號每次被觸發時都被會調用,與觸發信號的實例無關
- 使用
g_signal_override_class_closure
重寫類閉包,可在信號的繼承類型上調用此函數 - 使用
g_signal_connect
家族函數,這是一個實例范圍的操作:只有當給定的實例觸發信號時,才會調用閉包
使用g_signal_add_emission_hook
和g_signal_remove_emission_hook
可以創建全局的觸發鈎子,且與觸發信號的實例無關
如果關聯的函數的參數多於定義的信號函數,那么需要在關聯的時候傳入,例如:
### 信號聲明
struct _SpiceSessionClass
{
GObjectClass parent_class;
/* signals */
void (*channel_new)(SpiceSession *session, SpiceChannel *channel);
void (*channel_destroy)(SpiceSession *session, SpiceChannel *channel);
/*< private >*/
/*
* If adding fields to this struct, remove corresponding
* amount of padding to avoid changing overall struct size
*/
gchar _spice_reserved[SPICE_RESERVED_PADDING];
};
### 處理函數聲明
static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data);
### 關聯信號
g_signal_connect(conn->session, "channel-new", G_CALLBACK(channel_new), conn);
信號的觸發
使用g_signal_emit
家族函數來觸發信號
void g_signal_emitv (const GValue *instance_and_params,
guint signal_id,
GQuark detail,
GValue *return_value);
參數名 | 說明 |
---|---|
instance_and_params | GValues數組保存的信號的參數列表,數組的首元素是觸發信號的對象實例指針 |
signal_id | 被觸發的信號標識 |
detail | 描述信號被觸發的細節標識 |
return_value | 保存在沒有accumulator情況下最后一個閉包調用放返回值 |
g_signal_emit_by_name(session, "session-display-added", display);
信號觸發的五個階段:
階段 | 說明 |
---|---|
RUN_FIRST | 如果信號注冊時使用了G_SIGNAL_RUN_FIRST標志,並且存在類閉包,那么類閉包將被調用 |
EMISSION_HOOK | 如果信號被關聯了觸發鈎子,那么將按照關聯順序依次調用鈎子函數 |
HANDLER_RUN_FIRST | 使用g_signal_connect 關聯的閉包將按照關聯時的順序被依次調用 |
RUN_LAST | 如果信號注冊時使用了G_SIGNAL_RUN_LAST標志,並且存在類閉包,那么類閉包將被調用 |
HANDLER_RUN_LAST | 使用g_signal_connect_after 關聯的閉包如果沒有在HANDLER_RUN_FIRST中被調用,那么將按照關聯時的順序被依次調用 |
RUN_CLEANUP | 如果信號注冊時使用了G_SIGNAL_RUN_CLEANUP標志,並且存在類閉包,那么類閉包將被調用,信號發射到此為止 |
在信號發射的任意階段(除RUN_CLEANUP外),任意閉包調用g_signal_stop_emission
方法,都將使發射直接進入RUN_CLEANUP階段。
在信號發射的任意階段,如果有閉包或鈎子再次出發相同信號,都將使發射回到RUN_FIRST階段。
accumulator函數在所有階段(除了EMISSION_HOOK和RUN_CLEANUP)的閉包被調用后都會被執行,如果accumulator函數返回不為TRUE,都將使發射直接進入RUN_CLEANUP階段。