我第一次学习GValue应该是二零二一年九月九日,那是一段最美的时光。漫山遍野你的脸庞,唯有遗忘是最漫长。那时候刚接触GLib库,我在CSDN中,给这一概念放到了GLib分类中记录,其实是错误的。

GValue 是 GObject 系统中用于通用值存储的结构体。它被设计用来存储各种不同类型的值,包括基本数据类型、对象类型等。也就是说,所有GType类型系统注册的类型,都可以被GValue存储。

GValue 可以看作是一个变量容器,由类型标识符(GType)和类型的特定值组成。

要创建一个未定义的 GValue 结构体,并使用,有以下步骤注意:

  1. 只需创建一个零填充的 GValue 结构体即可。(因为该结构体里面有联合体,需要把所有空间置零)

  2. 要初始化 GValue,请使用 g_value_init() 函数。在初始化之前,GValue 不能被使用。(初始化的作用是:找到要关联GType类型的函数表,该函数表由此类型的copy,free等函数,拷贝或者初始化创建这个对象)

  3. 在销毁之前,你必须始终使用 g_value_unset() 来确保分配的内存被释放。(释放初始化创建的对象内存)

  4. 释放 GValue 结构体的内存

基本类型操作(如释放和复制)由存储在 GValue 中的类型 ID 关联的 GTypeValueTable 确定。其他 GValue 操作(如在类型之间转换值)由此接口提供。

1 GValue是GBoxed类型对象

查看Gboxed相关源文件和头文件,发现是在gobject目录下。GValue是一个Boxed类型对象。

/* filename: gboxed.h */
#define G_TYPE_VALUE (g_value_get_type ())
/* filename: gboxed.c */
#define G_TYPE_VALUE (g_value_get_type ())
G_DEFINE_BOXED_TYPE (GValue, g_value, value_copy, value_free)

static GValue *
value_copy (GValue *src_value)
{
  GValue *dest_value = g_new0 (GValue, 1);

  if (G_VALUE_TYPE (src_value))
    {
      g_value_init (dest_value, G_VALUE_TYPE (src_value));
      g_value_copy (src_value, dest_value);
    }
  return dest_value;
}

static void
value_free (GValue *value)
{
  if (G_VALUE_TYPE (value))
    g_value_unset (value);
  g_free (value);
}

2 GValue结构体定义

既然知道是GBoxed对象,那系统就没有自动申请内存和释放内存空间函数。

下面是 GValue 定义的结构体:

struct _GValue
{
  /*< private >*/
  GType		g_type; /* GValue存储的数据的数据类型 */

  /* public for GTypeValueTable methods */
  union {
    gint	v_int;
    guint	v_uint;
    glong	v_long;
    gulong	v_ulong;
    gint64      v_int64;
    guint64     v_uint64;
    gfloat	v_float;
    gdouble	v_double;
    gpointer	v_pointer;
  } data[2];
};
  • data:存储数据

3 GValue 使用

具体示例代码可以参考 /assets/GObjectStudy/202311/13_GValue/

3.1 创建一个零填充的 GValue 结构体

因为不是标准GObject对象,所以没有自动分配和释放内存函数。必须用零填充是因为使用了联合体。

栈区创建

GValue a = G_VALUE_INIT; /* #define G_VALUE_INIT  { 0, { { 0 } } } */

堆区创建

GValue *value = g_new0 (GValue, 1);

3.2 GValue初始化函数g_value_init

/* filename:gvalue.c */
GValue*
g_value_init (GValue *value,
	      GType   g_type)
{
  GTypeValueTable *value_table;
  /* g_return_val_if_fail (G_TYPE_IS_VALUE (g_type), NULL);	be more elaborate below */
  g_return_val_if_fail (value != NULL, NULL);
  /* g_return_val_if_fail (G_VALUE_TYPE (value) == 0, NULL);	be more elaborate below */

  value_table = g_type_value_table_peek (g_type);

  if (value_table && G_VALUE_TYPE (value) == 0)
    {
      /* setup and init */
      value_meminit (value, g_type);
      value_table->value_init (value);
    }
  else if (G_VALUE_TYPE (value))
    g_critical ("%s: cannot initialize GValue with type '%s', the value has already been initialized as '%s'",
	        G_STRLOC,
	        g_type_name (g_type),
	        g_type_name (G_VALUE_TYPE (value)));
  else /* !G_TYPE_IS_VALUE (g_type) */
    g_critical ("%s: cannot initialize GValue with type '%s', %s",
                G_STRLOC,
                g_type_name (g_type),
                value_table ? "this type is abstract with regards to GValue use, use a more specific (derived) type" : "this type has no GTypeValueTable implementation");
  return value;
}

如果第一步没有用零填充结构体,G_VALUE_TYPE (value) == 0 就不能通过。

关键函数为 value_meminit (value, g_type)value_table->value_init (value)

3.2.1 value_meminit (value, g_type)

static inline void		/* keep this function in sync with gvaluecollector.h and gboxed.c */
value_meminit (GValue *value,
	       GType   value_type)
{
  value->g_type = value_type;
  memset (value->data, 0, sizeof (value->data));
}

3.2.2 value_table->value_init (value)

value_table->value_init (value)具体指向那些函数,和类型注册函数有关,GObject类型对象一般都用的同一个 GTypeValueTableGBoxed类型对象也如此。

/* filename: gboxed.c */
static void
boxed_proxy_value_init (GValue *value)
{
  value->data[0].v_pointer = NULL;
}

GType
g_boxed_type_register_static (const gchar   *name,
			      GBoxedCopyFunc boxed_copy,
			      GBoxedFreeFunc boxed_free)
{
  static const GTypeValueTable vtable = {
    boxed_proxy_value_init,
    boxed_proxy_value_free,
    boxed_proxy_value_copy,
    boxed_proxy_value_peek_pointer,
    "p",
    boxed_proxy_collect_value,
    "p",
    boxed_proxy_lcopy_value,
  };
  GTypeInfo type_info = {
    0,			/* class_size */
    NULL,		/* base_init */
    NULL,		/* base_finalize */
    NULL,		/* class_init */
    NULL,		/* class_finalize */
    NULL,		/* class_data */
    0,			/* instance_size */
    0,			/* n_preallocs */
    NULL,		/* instance_init */
    &vtable,		/* value_table */
  };
  GType type;

  g_return_val_if_fail (name != NULL, 0);
  g_return_val_if_fail (boxed_copy != NULL, 0);
  g_return_val_if_fail (boxed_free != NULL, 0);
  g_return_val_if_fail (g_type_from_name (name) == 0, 0);

  type = g_type_register_static (G_TYPE_BOXED, name, &type_info, 0);

  /* install proxy functions upon successful registration */
  if (type)
    _g_type_boxed_init (type, boxed_copy, boxed_free);

  return type;
}

GObject对象类型 GTypeValueTable

/* filename: gobject.c */
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 */
};

一般对象类型,都是把 value->data[0].v_pointer = NULL,也就是使用的是指针变量。

3.3 g_value_unset

/* filename: gvalue.c */
void
g_value_unset (GValue *value)
{
  GTypeValueTable *value_table;
  
  if (value->g_type == 0)
    return;

  g_return_if_fail (value);

  value_table = g_type_value_table_peek (G_VALUE_TYPE (value));
  g_return_if_fail (value_table);

  if (value_table->value_free)
    value_table->value_free (value);
  memset (value, 0, sizeof (*value));
}



/* filename: gboxed.c */
static void
boxed_proxy_value_free (GValue *value)
{
  if (value->data[0].v_pointer && !(value->data[1].v_uint & G_VALUE_NOCOPY_CONTENTS))
    /*如果是通过拷贝函数*/
    _g_type_boxed_free (G_VALUE_TYPE (value), value->data[0].v_pointer);
}

3.4 释放GValue结构体内存

如果通过堆区创建的GValue结构体

g_free (value)

4 补充

4.1 为什么用户一定要编写GBoxed拷贝和释放函数

因为 g_value_set_boxed 调用 value_set_boxed_internal

下面函数使用了 g_boxed_copy

static inline void
value_set_boxed_internal (GValue       *value,
			  gconstpointer boxed,
			  gboolean      need_copy,
			  gboolean      need_free)
{
  if (!boxed)
    {
      /* just resetting to NULL might not be desired, need to
       * have value reinitialized also (for values defaulting
       * to other default value states than a NULL data pointer),
       * g_value_reset() will handle this
       */
      g_value_reset (value);
      return;
    }

  if (value->data[0].v_pointer && !(value->data[1].v_uint & G_VALUE_NOCOPY_CONTENTS))
    g_boxed_free (G_VALUE_TYPE (value), value->data[0].v_pointer);
  value->data[1].v_uint = need_free ? 0 : G_VALUE_NOCOPY_CONTENTS;
  value->data[0].v_pointer = need_copy ? g_boxed_copy (G_VALUE_TYPE (value), boxed) : (gpointer) boxed;
}