最近在分析Gstreamer的代碼時,發現GstPipeline中有以下代碼:html
result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
但搜索當前文件並無發現有對parent_class變量的定義,查詢後發現這是GObject在相應宏展開時所定義的一個靜態變量,當使用G_DEFINE_TYPE宏定義一個GObject對象時,宏會自動展開生成相應的代碼,子類能夠經過parent_class調用父類的函數。下面對這種使用方式作一個簡單分析。git
在GObject的介紹中提到,父類的結構體必須做爲子類結構體的第一個成員,因爲C語言中定義第一個結構體數據必須存放在所分配內存的起始位,因此這樣能夠經過快速強制轉換訪問父類成員,這也是C語言裏的一個重要的技巧,示例以下:github
1 struct _GTypeClass{ 2 GType g_type; 3 }; 4 struct _GTypeInstance{ 5 GTypeClass *g_class; 6 }; 7 /* A definitions */ 8 typedef struct { 9 GTypeInstance parent; 10 int field_a; 11 int field_b; 12 } A; 13 typedef struct { 14 GTypeClass parent_class; 15 void (*method_a) (void); 16 void (*method_b) (void); 17 } AClass; 18 19 /* B definitions. */ 20 typedef struct { 21 A parent; 22 int field_c; 23 int field_d; 24 } B; 25 typedef struct { 26 AClass parent_class; 27 void (*method_c) (void); 28 void (*method_d) (void); 29 } BClass; 30 31 B *b; 32 b->parent.parent.g_class->g_type //普通順序訪問 33 ((GTypeInstance *) b)->g_class->g_type //快速強制轉換
一樣在GstPipeline的聲明中,父類一樣是做爲子類的第一個結構體成員:ide
1 #define GST_TYPE_PIPELINE (gst_pipeline_get_type ()) 2 3 struct _GstPipeline { 4 GstBin bin; 5 ... 6 }; 7 8 struct _GstPipelineClass { 9 GstBinClass parent_class; 10 ... 11 };
爲了能讓GObject在new出一個對象的時候順利找到相應類型,必須將自定義的類添加到類型系統裏面,GObject提供了一系列的 G_DEFINE_TYPE 宏來快速定義一個新的對象類型,GstPipeline則經過下列宏定義對象:函數
1 G_DEFINE_TYPE_WITH_CODE (GstPipeline, gst_pipeline, GST_TYPE_BIN, _do_init);
咱們能夠經過GCC的預處理來展開宏,查看生成的代碼:spa
1 $ cd gstreamer-1.14.4 2 $ gcc -E `pkg-config --cflags glib-2.0` -I. gst/gstpipeline.c > gstpipeline_preposs.c 3 $ clang-format -i gstpipeline_preposs.c
宏展開後,主要生成一個父類class對象的指針(parent_class),class對象的內部初始化接口(gst_pipeline_class_intern_init)獲取惟一標識當前類型的接口(gst_pipeline_get_type),parent_class也會在class init的時候進行初始化:指針
1 static void gst_pipeline_init(GstPipeline *self); 2 static void gst_pipeline_class_init(GstPipelineClass *klass); 3 static gpointer parent_class = ((void *)0) ; 4 5 // init parent_class pointer to GstBinClass 6 // and call user defined class init function. 7 static void gst_pipeline_class_intern_init(gpointer klass) { 8 parent_class = g_type_class_peek_parent(klass); 9 gst_pipeline_class_init((GstPipelineClass *)klass); 10 } 11 12 // generate unique id for each class 13 // and register instance and class init functions 14 GType gst_pipeline_get_type(void) { 15 static volatile gsize g_define_type_id__volatile = 0; 16 if ( g_once_init_enter(&g_define_type_id__volatile)) { 17 GType g_define_type_id = g_type_register_static_simple( 18 (gst_bin_get_type()), /* GST_TYPE_BIN parent type*/ 19 g_intern_static_string("GstPipeline"), /* type name */ 20 sizeof(GstPipelineClass), /* class size */ 21 (GClassInitFunc)gst_pipeline_class_intern_init, /* class init */ 22 sizeof(GstPipeline), /* instance size */ 23 (GInstanceInitFunc)gst_pipeline_init, /* instance init */ 24 (GTypeFlags)0); /* flags */ 25 ... 26 g_once_init_leave((&g_define_type_id__volatile), (gsize)(g_define_type_id)); 27 } 28 return g_define_type_id__volatile; 29 }
建立GObject對象須要經過g_object_new(),這個接口的第一個函數就是須要建立對象的類型,好比咱們能夠經過以下方式建立GstPipeline對象:code
1 GstPipeline *pipeline = g_object_new (GST_TYPE_PIPELINE, "name", "pipeline1", NULL);
這裏的GST_TYPE_PIPELINE是GstPipeline的類型,這個宏被定義爲gst_pipeline_get_type(),這個函數經過上面提到的宏展開獲得。orm
當對象被建立時,咱們定義的類對象(GstPipelineClass)初始化函數gst_pipeline_class_init() 被調用,而後對象實例(GstPipeline)初始化函數被調用 gst_pipeline_init()。htm
這裏之因此須要持有父類class對象的指針,是由於當子類在執行class init時,能夠覆蓋父類的方法,若是將子類class對象強制轉換爲父類class對象,調用的方法任然是子類重寫了的方法,因此這裏必須持有父類class對象的指針(parent_class),才能夠調用父類的方法。
在子類中調用父類方法,咱們只需將parent_class轉換爲父類class對象,經過函數指針調用便可。例如GstPipeline中獲取時鐘的方法:
1 GST_ELEMENT_CLASS (parent_class)->provide_clock (GST_ELEMENT(pipeline));
因爲GstPipeline的父類是GstBin,GstBin的父類是GstElement,在GstBin的class init函數gst_bin_class_init中,provide_clock被初始化爲 gst_bin_provide_clock_func,在子類GstPipeline的class init函數gst_pipeline_class_init中,provide_clock被初始化爲gst_pipeline_provide_clock_func。
所以在GstPipeline中就能夠經過parent_class調用GstBin中的接口。
這套機制只能保證子類能夠調用父類的方法,可能能顯示調用父類以上基類的接口,例如上例中,若是GstElement中也有一個provide_clock的實現,則在GstPipeline中沒法直接調用GstElement中的實現。
在G_DEFINE_TYPE宏的實現中,parent_class還帶有一個前綴,以下:
1 #define _G_DEFINE_TYPE_EXTENDED_BEGIN_PRE(TypeName, type_name, TYPE_PARENT) \ 2 ...\ 3 static gpointer type_name##_parent_class = NULL; \ 4 ...
但實際展開的代碼爲:
1 static gpointer parent_class = ((void *)0) ;
爲何這裏會去掉type_name還不得而知。
20190517更新:
由於在G_DEFINE_TYPE前,將gst_pipeline_parent_class定義爲一個宏,因此G_DEFINE_TYPE展開後,及爲parent_class.
1 #define gst_pipeline_parent_class parent_class 2 G_DEFINE_TYPE_WITH_CODE (GstStream, gst_stream, GST_TYPE_OBJECT, _do_init);
https://github.com/GNOME/glib/blob/master/gobject/gtype.h
https://developer.gnome.org/gobject/stable/gobject-Type-Information.htm
https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html
https://developer.gnome.org/gobject/stable/gtype-instantiable-classed.html