了解GObject

glib是一个跨平台实现的c语言基础库,实现了众多的基础功能。比如:

  1. 基本的数据结构:动态数组GArray,单/双向链表GSList/GList,字符串数组GString,哈希字典GHashTable,队列GQueue/GAsyncQueue,平衡二叉树GTree,任意类型结构GVariant等等。
  2. 基本工具类型:数据校验GChecksum,时间GDate, GDateTime, GTimer,通用错误类型GError,随机数GRand,正则表达式GPatternSpec, g_regex_*(),资源地址GUri等等。
  3. 多线程相关:主线程执行GMainLoop, GMainContext, 线程GThread, GThreadPool, 同步器GCond, 单次执行工具GOnce,递归锁GRecMutex, 单个线程能锁多次而不死锁,读写锁GRWLock,线程锁GMutex
  4. 还有一些其它功能。

很多著名的开源项目都基于此完成的。比如 gtk+,gnome等等。而在WebRTC中的janus-gateway, kurento也都是基于glib实现的。

现在我们来了解一下glib中的核心结构gobject的实现细节。

首先查看手册,看看其中的基本介绍,和使用示例等。

摘抄总结如下:

重要的方面
1. 一个通用的类型系统,能够注册任意单层或多层单继承的结构体,或则接口。它能够管理相关对象创建,初始化,内存管理,维护父子关系,处理接口继承的动态实现。也就是说对象的指定实现是能够动态替换的。
2. 基础类型的实现。
3. GObject作为所有类型的基类。
4. 一个信号通知系统,能够灵活的让用户自定义重写对象的方法。
5. 一个可扩展的参数、值系统,支持对象的属性或者参数化类型。

主要提供两方面的功能:
1. 面向对象的基于C的API。
2. 自动且透明的API绑定到其它语言。

GObject主要实现了一下三个方面:

  1. 类型的自动构造和析构。
  2. 继承动态实现,采用运行时类型系统完成。
  3. 属性功能。
  4. 信号的发送与接受。

1. 类型的自动构造和析构

C语言本身不提供任何面向对象的特性,因此需要手动来写。我们知道C++实现此功能是在Class内部加入了一张虚函数表(vtable)来实现的。因此C语言实现此功能也差不多是这样。
GObject把功能一分为二,定义了两个结构体GObjectGObjectClass,且为每一个对象分配了一个唯一的ID

GObject

struct  _GObject
{
  GTypeInstance  g_type_instance;
  
  /*< private >*/
  guint          ref_count;  /* (atomic) */
  GData         *qdata;
};

ref_count主要是处理生命周期
·qdata`主要是处理属性及信号的发送、接受

GObjectClass

struct  _GObjectClass
{
  GTypeClass   g_type_class;

  /*< private >*/
  GSList      *construct_properties;

  /*< public >*/
  /* seldom overridden */
  GObject*   (*constructor)     (GType                  type,
                                 guint                  n_construct_properties,
                                 GObjectConstructParam *construct_properties);
  /* overridable methods */
  void       (*set_property)        (GObject        *object,
                                         guint           property_id,
                                         const GValue   *value,
                                         GParamSpec     *pspec);
  void       (*get_property)        (GObject        *object,
                                         guint           property_id,
                                         GValue         *value,
                                         GParamSpec     *pspec);
  void       (*dispose)         (GObject        *object);
  void       (*finalize)        (GObject        *object);
  /* seldom overridden */
  void       (*dispatch_properties_changed) (GObject      *object,
                         guint     n_pspecs,
                         GParamSpec  **pspecs);
  /* signals */
  void       (*notify)          (GObject    *object,
                     GParamSpec *pspec);

  /* called when done constructing */
  void       (*constructed)     (GObject    *object);

  /*< private >*/
  gsize     flags;

  /* padding */
  gpointer  pdummy[6];
};

对象的方法

了解完这些后,我们看如何用类型ID及全局函数关联上这些方法。
首先查看全局对象的管理,在_g_object_type_init中调用g_type_register_fundamental注册上了GObject类型

  GTypeInfo info = {
    sizeof (GObjectClass),
    (GBaseInitFunc) g_object_base_class_init,
    (GBaseFinalizeFunc) g_object_base_class_finalize,
    (GClassInitFunc) g_object_do_class_init,
    NULL    /* class_destroy */,
    NULL    /* class_data */,
    sizeof (GObject),
    0       /* n_preallocs */,
    (GInstanceInitFunc) g_object_init,
    NULL,   /* value_table */
  };
  static const GTypeValueTable value_table = {
    g_value_object_init,      /* value_init */
    g_value_object_free_value,    /* value_free */
    g_value_object_copy_value,    /* value_copy */
    g_value_object_peek_pointer,  /* value_peek_pointer */
    "p",              /* collect_format */
    g_value_object_collect_value, /* collect_value */
    "p",              /* lcopy_format */
    g_value_object_lcopy_value,   /* lcopy_value */
  };

  info.value_table = &value_table;
  g_type_register_fundamental (G_TYPE_OBJECT, g_intern_static_string ("GObject"), &info, &finfo, 0);
  g_value_register_transform_func (G_TYPE_OBJECT, G_TYPE_OBJECT, g_value_object_transform_value);

这里会把构造及析构相关函数挂载上,最终插入到一个全局HashTable上static_type_nodes_ht,并且分配一个特定类型ID(20)给GObject,由于这个调用是在glib初始化中完成,所以GObject的类型ID固定为20。如果要把此库移植到特定平台上,要裁剪一些基础类型,可能会导致ID变化。实际运行中,对ID做了一个位移的处理,我也没看到处理的理由。
我们分析g_object_new的逻辑看看,以下仅提取部分代码,并且把调用堆栈去掉了(inline)

gpointer
g_object_new (GType    object_type,
          const gchar *first_property_name,
          ...)
{
  // 获取类型对应的GObjectClass对象
  // 这里采用了懒加载的方式,即需要使用的时候才去初始化class对象。默认是未初始化的。
  // 初始化会先分配内存,然后用上面注册的方法去初始化。
  class = g_type_class_peek_static (object_type);

  // 分配对象
  // 中间主要是计算需要分配的内存大小,及父类的初始化,由于只支持单继承,故初始化顺序很好确定。
  object = (GObject *) g_type_create_instance (class->g_type_class.g_type);
}

这里省略了TypeNode的数据结构介绍。我们来理清几个关键的逻辑

  1. 继承关系的确定
  2. 重写的实现方式
  3. 属性的实现方式
  4. 信号的实现方式

1. 继承关系的确定

struct _TypeNode
{
...
  guint        n_children; /* writable with lock */
  guint        n_supers : 8;
  GType       *children; /* writable with lock */
  GType        supers[1]; /* flexible array */
};

摘取一部分定义,可以看到TypeNode中把所有的父类及自身放到了supers中,把子类放到了children中。这样就可以明确的知道了父类和子类的关系了。而且superschildren中存储的就是对应的TypeNode指针。
这里需要说明的是,基础类型和自定义类型的区别,由于基础类型没有父类,所以做了独立的处理。

2. 重写的实现

1. 接口的实现

首先添加实现接口

// derived_object_type 子类型
// TEST_TYPE_IFACE 接口类型
// iface_info 实现接口相关参数,初始化,析构,特定数据。
// 这里会根据iface_info 回调对应的初始化函数,然后手动挂载对应的实现到虚函数表
g_type_add_interface_static (derived_object_type, TEST_TYPE_IFACE, &iface_info);

而虚函数表保存在这里

struct _TypeNode
{
...
GData       *global_gdata;
  union {
    GAtomicArray iface_entries;     /* for !iface types */
    GAtomicArray offsets;
  } _prot;
...
};

iface_entries 保持的是IFaceEntries指针,IFaceEntry保存了虚函数表,这个vtable定义和使用并不一致,定义为GTypeInterface,在实际使用中保持了实际接口的结构体对象的指针。

普通的虚函数实现

struct _GCustomObjectClass
{
  GObjectClass parent_class;
...
}

g_object_set_property举例:
在class的构造函数中,直接把自己的g_custom_object_set_property给替换g_object_set_property就行了。

这里由于是单继承,且父类属性放到前面,所以任何类型的指针都可以直接转成父类指针使用。

3. 属性的实现方式

属性的两个函数

GLIB_AVAILABLE_IN_ALL
void        g_object_set_property             (GObject        *object,
                           const gchar    *property_name,
                           const GValue   *value);
GLIB_AVAILABLE_IN_ALL
void        g_object_get_property             (GObject        *object,
                           const gchar    *property_name,
                           GValue         *value);

而GObject的两个虚方法定义

struct  _GObjectClass
{
...
  /* overridable methods */
  void       (*set_property)        (GObject        *object,
                                         guint           property_id,
                                         const GValue   *value,
                                         GParamSpec     *pspec);
  void       (*get_property)        (GObject        *object,
                                         guint           property_id,
                                         GValue         *value,
                                         GParamSpec     *pspec);
...
};

首先有个全局属性表pspec_pool来保存属性对应关系,及类型和属性名字,或者属性id对应的实现类型。然后根据继承关系,确定set_property/get_property的实现函数,调取相应的函数来实现功能。
注意GObject并没有实现属性的存取。需要自己实现测功能,否则仅会输出警告。

4. 信号的实现方式

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容