Glib之GObject簡介(翻譯)


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_notifierg_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_newvg_signal_new_valistg_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_hookg_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_HOOKRUN_CLEANUP)的閉包被調用后都會被執行,如果accumulator函數返回不為TRUE,都將使發射直接進入RUN_CLEANUP階段。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM