美文网首页
了解GObject

了解GObject

作者: 通往心的路 | 来源:发表于2021-08-25 18:34 被阅读0次

    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

          本文链接:https://www.haomeiwen.com/subject/ejcdiltx.html