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

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

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

强制转换

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

 struct _GTypeClass{
GType g_type;
};
struct _GTypeInstance{
GTypeClass *g_class;
};
/* A definitions */
typedef struct {
GTypeInstance parent;
int field_a;
int field_b;
} A;
typedef struct {
GTypeClass parent_class;
void (*method_a) (void);
void (*method_b) (void);
} AClass; /* B definitions. */
typedef struct {
A parent;
int field_c;
int field_d;
} B;
typedef struct {
AClass parent_class;
void (*method_c) (void);
void (*method_d) (void);
} BClass; B *b;
b->parent.parent.g_class->g_type //普通顺序访问
((GTypeInstance *) b)->g_class->g_type //快速强制转换

同样在GstPipeline的声明中,父类同样是作为子类的第一个结构体成员:

 #define GST_TYPE_PIPELINE               (gst_pipeline_get_type ())

 struct _GstPipeline {
GstBin bin;
...
}; struct _GstPipelineClass {
GstBinClass parent_class;
...
};

G_DEFINE_TYPE宏

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

 G_DEFINE_TYPE_WITH_CODE (GstPipeline, gst_pipeline, GST_TYPE_BIN, _do_init);

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

 $ cd gstreamer-1.14.
$ gcc -E `pkg-config --cflags glib-2.0` -I. gst/gstpipeline.c > gstpipeline_preposs.c
$ clang-format -i gstpipeline_preposs.c

宏展开后,主要生成一个父类class对象的指针(parent_class),class对象的内部初始化接口(gst_pipeline_class_intern_init)获取唯一标识当前类型的接口(gst_pipeline_get_type),parent_class也会在class init的时候进行初始化:

 static void gst_pipeline_init(GstPipeline *self);
static void gst_pipeline_class_init(GstPipelineClass *klass);
static gpointer parent_class = ((void *)) ; // init parent_class pointer to GstBinClass
// and call user defined class init function.
static void gst_pipeline_class_intern_init(gpointer klass) {
parent_class = g_type_class_peek_parent(klass);
gst_pipeline_class_init((GstPipelineClass *)klass);
} // generate unique id for each class
// and register instance and class init functions
GType gst_pipeline_get_type(void) {
static volatile gsize g_define_type_id__volatile = ;
if ( g_once_init_enter(&g_define_type_id__volatile)) {
GType g_define_type_id = g_type_register_static_simple(
(gst_bin_get_type()), /* GST_TYPE_BIN parent type*/
g_intern_static_string("GstPipeline"), /* type name */
sizeof(GstPipelineClass), /* class size */
(GClassInitFunc)gst_pipeline_class_intern_init, /* class init */
sizeof(GstPipeline), /* instance size */
(GInstanceInitFunc)gst_pipeline_init, /* instance init */
(GTypeFlags)); /* flags */
...
g_once_init_leave((&g_define_type_id__volatile), (gsize)(g_define_type_id));
}
return g_define_type_id__volatile;
}

创建GObject对象

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

 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中获取时钟的方法:

 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还带有一个前缀,如下:

 #define _G_DEFINE_TYPE_EXTENDED_BEGIN_PRE(TypeName, type_name, TYPE_PARENT) \
...\
static gpointer type_name##_parent_class = NULL; \
...

但实际展开的代码为:

 static gpointer parent_class = ((void *)) ;

为什么这里会去掉type_name还不得而知。

20190517更新:

因为在G_DEFINE_TYPE前,将gst_pipeline_parent_class定义为一个宏,所以G_DEFINE_TYPE展开后,及为parent_class.

 #define gst_pipeline_parent_class parent_class
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
本文版权归作者所有,欢迎转载。商业转载请联系作者获得授权,非商业转载请在文章页面明显位置给出原文连接.

GObject调用父类函数的更多相关文章

  1. cocos2dx 3.x(获得父类的node型指针调用父类函数this->getParent())

    void CenterLayer::zhanzheng(CCObject* pSender){ ((GameScene*)this->getParent())->showLayer(Gam ...

  2. C++/JAVA/C#子类调用父类函数情况[留存]

    时间久了就容易记不清了,特留存备用查看 c++ 1.构造函数调用   常用初始化列表  或者显示调用 1.1同一个类中构造函数调用构造函数   尽量不要这样做,因为结果不确定!避免麻烦(C++11增加 ...

  3. Qt 学习之路 2(19):事件的接受与忽略(当重写事件回调函数时,时刻注意是否需要通过调用父类的同名函数来确保原有实现仍能进行!有好几个例子。为什么要这么做?而不是自己去手动调用这两个函数呢?因为我们无法确认父类中的这个处理函数有没有额外的操作)

    版本: 2012-09-29 2013-04-23 更新有关accept()和ignore()函数的相关内容. 2013-12-02 增加有关accept()和ignore()函数的示例. 上一章我们 ...

  4. JavaScript中子类调用父类方法的实现

    一.前言 最近在项目中,前端框架使用JavaScript面向对象编程,遇到了诸多问题,其中最典型的问题就是子类调用父类(super class)同名方法,也就是如C#中子类中调用父类函数base.** ...

  5. python使用super()调用父类的方法

    如果要在子类中引用父类的方法,但是又需要添加一些子类所特有的内容,可通过类名.方法()和super()来调用父类的方法,再个性化子类的对应函数. 直接使用类名.方法()来调用时,还是需要传入self为 ...

  6. 【Python】Python中子类怎样调用父类方法

    python中类的初始化方法是__init__(),因此父类子类的初始化方法都是这个,如果子类不实现这个函数,初始化时调用父类的初始化函数,如果子类实现这个函数,就覆盖了父类的这个函数,既然继承父类, ...

  7. Python子类调用父类内属性的方法

    常见的就是初始化方法__init__() python中类的初始化方法是__init__(),因此父类子类的初始化方法都是这个,如果子类不实现这个函数,初始化时调用父类的初始化函数,如果子类实现这个函 ...

  8. 23.C++- 继承的多种方式、显示调用父类构造函数、父子之间的同名函数、virtual虚函数

     上章链接: 22.C++- 继承与组合,protected访问级别 继承方式 继承方式位于定义子类的”:”后面,比如: class Line : public Object //继承方式是publi ...

  9. accept()函数用来告诉Qt,事件处理函数“接收”了这个事件,不要再传递;ignore()函数则告诉Qt,事件处理函数“忽略”了这个事件,需要继续传递(看一下QWidget::mousePressEvent的实现,最为典型。如果希望忽略事件,只要调用父类的响应函数即可)

    QEvent的accept()和ignore()一般不会用到,因为不如直接调用QWidget类的事件处理函数直接,而且作用是一样的,见下面的例子. 推荐直接调用QWidget的事件处理函数.而不是调用 ...

随机推荐

  1. 用SQL Server验证用户名和密码

    用SQL Server验证用户名和密码,从页面输入的用户名和密码与数据库的用户名和密码进行匹配,正确则登入,错误则提醒. <form action="index.jsp" m ...

  2. CXF-JAX-RS开发(二)spring整合CXF-JAX-RS

    一.创建maven工程[Packaging:war] 1.目录结构 2.坐标书写 二.导入依赖和tomcat服务器插件 <dependencies> <!-- CXF --> ...

  3. ROS:Nvidia Jetson TK1开发平台

    原文链接: http://wiki.ros.org/NvidiaJetsonTK1 1. Nvidia Jetson TK1 Jetson TK1 comes pre-installed with L ...

  4. beetl模板入门例子

    加入maven依赖 <dependency> <groupId>org.beetl</groupId> <artifactId>beetl-core&l ...

  5. 备份-泛函编程(23)-泛函数据类型-Monad

    泛函编程(23)-泛函数据类型-Monad http://www.cnblogs.com/tiger-xc/p/4461807.html https://blog.csdn.net/samsai100 ...

  6. ORACLE 查询不走索引的原因分析,解决办法通过强制索引或动态执行SQL语句提高查询速度

    (一)索引失效的原因分析: <>或者单独的>,<,(有时会用到,有时不会) 有时间范围查询:oracle 时间条件值范围越大就不走索引 like "%_" ...

  7. LOJ #6041. 「雅礼集训 2017 Day7」事情的相似度 LCT+SAM+线段树

    Code: #include<bits/stdc++.h> #define maxn 200003 using namespace std; void setIO(string s) { ...

  8. anaconda安装的TensorFlow版本没有model这个模块

    一.采用git bash来安装,确认已经安装了git 二.手动找到TensorFlow的模块文件夹地址,若不知道,输入以下两行代码: import tensorflow as tf tf.__path ...

  9. 布尔类型、操作符别名、C++函数、动态内存分配(new\delete)、引用(day02)

    六 C++的布尔类型 bool类型是C++中基本类型,专门表示逻辑值:true/false bool在内存上占一个字节:1表示true,0表示false bool类型可以接收任意类型和表达式的结果,其 ...

  10. 虚拟机pycharm

    一.进入虚拟机Ubuntu软件查找pycharm专业版进行安装 二.进入https://www.jetbrains.com/pycharm/download/#section=linux 下载Linu ...