zoukankan      html  css  js  c++  java
  • GObject调用父类函数

    最近在分析Gstreamer的代码时,发现GstPipeline中有如下代码:

    result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);

    但搜索当前文件并没有发现有对parent_class变量的定义,查询后发现这是GObject在相应宏展开时所定义的一个静态变量,当使用G_DEFINE_TYPE宏定义一个GObject对象时,宏会自动展开生成相应的代码,子类可以通过parent_class调用父类的函数。下面对这种使用方式做一个简单分析。

    强制转换

    GObject的介绍中提到,父类的结构体必须作为子类结构体的第一个成员,由于C语言中定义第一个结构体数据必须存放在所分配内存的起始位,所以这样可以通过快速强制转换访问父类成员,这也是C语言里的一个重要的技巧,示例如下:

     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的声明中,父类同样是作为子类的第一个结构体成员:

     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 };

    G_DEFINE_TYPE宏

    为了能让GObject在new出一个对象的时候顺利找到相应类型,必须将自定义的类添加到类型系统里面,GObject提供了一系列的 G_DEFINE_TYPE 宏来快速定义一个新的对象类型,GstPipeline则通过下列宏定义对象:

    1 G_DEFINE_TYPE_WITH_CODE (GstPipeline, gst_pipeline, GST_TYPE_BIN, _do_init);

     我们可以通过GCC的预处理来展开宏,查看生成的代码:

    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对象

    创建GObject对象需要通过g_object_new(),这个接口的第一个函数就是需要创建对象的类型,比如我们可以通过如下方式创建GstPipeline对象:

    1 GstPipeline *pipeline = g_object_new (GST_TYPE_PIPELINE, "name", "pipeline1", NULL);

    这里的GST_TYPE_PIPELINE是GstPipeline的类型,这个宏被定义为gst_pipeline_get_type(),这个函数通过上面提到的宏展开得到。

    当对象被创建时,我们定义的类对象(GstPipelineClass)初始化函数gst_pipeline_class_init() 被调用,然后对象实例(GstPipeline)初始化函数被调用 gst_pipeline_init()。

    这里之所以需要持有父类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

    作者:John.Leng
    本文版权归作者所有,欢迎转载。商业转载请联系作者获得授权,非商业转载请在文章页面明显位置给出原文连接.
  • 相关阅读:
    spring 事务
    Servlet详解之两个init方法的作用
    被request.getLocalAddr()苦闷了很久
    Java获取IP地址:request.getRemoteAddr()警惕
    MongoDB笔记
    hexo+github搭建博客
    Python处理Excel(使用openpyxl库)
    Wireshark使用学习
    查看开启操作系统端口
    记录Centos7服务器搭建过程
  • 原文地址:https://www.cnblogs.com/xleng/p/10763739.html
Copyright © 2011-2022 走看看