了解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. 信号的实现方式

你可能感兴趣的:(了解GObject)