Jan 28

    注:之前曾写过一篇类似文章,结果被我误删了

 

    按照GObject手册的说法,Boxed Type机制是用来包装(wrap)C语言结构体的一种机制。这里所谓的包装,其实就是在GType类型系统中注册该类型,并且该类型将成为GBoxed的子类。之后,这一类型就能够使用和GBoxed类型有关的所有函数了,如g_value_get_boxed等等。

    首先,我们看一下GBoxed在GType类型系统中的“注册信息”:

void
g_boxed_type_init (void)
{
  static const GTypeInfo info = {
    0,                          /* class_size */
    NULL,                       /* base_init */
    NULL,                       /* base_destroy */
    NULL,                       /* class_init */
    NULL,                       /* class_destroy */
    NULL,                       /* class_data */
    0,                          /* instance_size */
    0,                          /* n_preallocs */
    NULL,                       /* instance_init */
    NULL,                       /* value_table */
  };
  const GTypeFundamentalInfo finfo = { G_TYPE_FLAG_DERIVABLE, };
  GType type;

  /* G_TYPE_BOXED
   */
  type = g_type_register_fundamental (G_TYPE_BOXED, g_intern_static_string ("GBoxed"), &info, &finfo,
				      G_TYPE_FLAG_ABSTRACT | G_TYPE_FLAG_VALUE_ABSTRACT);
  g_assert (type == G_TYPE_BOXED);
}

    从以上信息中,我们可以看出,GBoxed是一个基本类型,不可实例化(instantiable)、不可类化(classed),可以被继承,但不可以被深继承(即继承了GBoxed的子类不能再被继承)。

    要想在GType类型系统中注册一个Boxed Type,我们需要提供一个copy_func和一个free_func,从名字上也可以看出,前者负责该类型的拷贝,后者负责该类型的释放。

    为更方便地说明问题,我们假设我们需要包装以下的StudentInfo类型:

typedef struct _StudentInfo StudentInfo;

struct _StudentInfo
{
	gint number;
	GString *name;
	gdouble grade;
};

    为了包装该类型,我们自然需要提供一个copy_func和一个free_func:

StudentInfo *student_info_new(gint number,gchar *name,gdouble grade)
{
	StudentInfo *new_std;

	new_std=g_new(StudentInfo,1);

	new_std->number=number;
	new_std->name=g_string_new(name);
	new_std->grade=grade;

	return new_std;
}

StudentInfo *student_info_copy(const StudentInfo *std_info)
{
	StudentInfo *new_std;

	new_std=g_new(StudentInfo,1);

	new_std->number=std_info->number;
	new_std->name=g_string_new(std_info->name->str);
	new_std->grade=std_info->grade;

	return new_std;
}

    下面,我们需要一个student_info_type_init函数来承担运行时刻注册该类型的任务。为方便起见,我们也为该函数配套了一个宏:

GType student_info_get_type()
{
    static GType student_info_id=0;

    if (student_info_id==0)
    {
        student_info_id=g_boxed_type_register_static("StudentInfo",(GBoxedCopyFunc)student_info_copy,(GBoxedFreeFunc)student_info_free);
    }

    return student_info_id;
}


#define TYPE_STUDENT_INFO (student_info_get_type())

    从上面的代码中可以看出,Boxed Type的注册工作实质上是由g_boxed_type_register_static函数完成的,该函数原型如下:

GType  g_boxed_type_register_static (const gchar *name,
                                     GBoxedCopyFunc boxed_copy,
                                     GBoxedFreeFunc boxed_free);

    该函数有三个参数,其中name是Boxed Type的名字,GBoxedCopyFunc和GBoxedFreeFunc则是用typedef定义的函数指针类型:

typedef gpointer (*GBoxedCopyFunc) (gpointer boxed);
typedef void (*GBoxedFreeFunc) (gpointer boxed);  

    至此,包装工作就完成了。

    但是上面给出的student_info_get_type()函数有一个问题:它不是线程安全的。而且,每次写这么多也显得很臃肿,为此,我们可以使用一个宏G_DEFINE_BOXED_TYPE:

#define G_DEFINE_BOXED_TYPE(TypeName, type_name, copy_func, free_func) G_DEFINE_BOXED_TYPE_WITH_CODE (TypeName, type_name, copy_func, free_func, {})

    该宏有4个参数:

  • TypeName: 该参数是该Boxed Type的名字,且不用引号包围。
  • type_name: 该参数将成为_get_type()的前缀,即最终自动生成的函数的名字为type_name_get_type()
  • copy_func: 该参数为函数指针或函数名(函数指针常量?),其类型应为以下三种之一:
    typedef gpointer (*copy_func) (gpointer boxed);
    typedef TypeName *(*copy_func) (const TypeName *boxed);
    typedef TypeName *(*copy_func) (TypeName *boxed);
  • free_func: 该参数为函数指针或函数名(函数指针常量?),其类型应为以下两种之一:
    typedef void (*free_func) (gpointer boxed);
    typedef void (*free_func) (TypeName *boxed);

    因此,如果想生成student_info_get_type()函数的话,只要按如下所示填写参数:

G_DEFINE_BOXED_TYPE(StudentInfo,student_info,student_info_copy,student_info_free);

    如果你由于种种原因而需要自己写*_get_type()函数的话,为使其线程安全,可以将以下代码作为模板使用:

GType
g_strv_get_type (void)
{
  static volatile gsize g_define_type_id__volatile = 0;

  if (g_once_init_enter (&g_define_type_id__volatile))
    {
      GType g_define_type_id =
        g_boxed_type_register_static (g_intern_static_string ("GStrv"),
                                      (GBoxedCopyFunc) g_strdupv,
                                      (GBoxedFreeFunc) g_strfreev);

      g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
    }

  return g_define_type_id__volatile;
}

    事实上,很久以前,G_DEFINE_BOXED_TYPE展开以后,也是和上面的代码类似的——之所以要改用现在的写法,主要还是为了增强对类型的检测。

    完整的测试代码如下:

#include <glib.h>
#include <glib-object.h>
#include <stdlib.h>

typedef struct _StudentInfo StudentInfo;

struct _StudentInfo
{
	gint number;
	GString *name;
	gdouble grade;
};

StudentInfo *student_info_new(gint number,gchar *name,gdouble grade)
{
	StudentInfo *new_std;

	new_std=g_new(StudentInfo,1);

	new_std->number=number;
	new_std->name=g_string_new(name);
	new_std->grade=grade;

	return new_std;
}

void student_info_print(const StudentInfo *std_info)
{
	g_print("Number:%3d Name:%5s Grade:%3.1f",std_info->number,std_info->name->str,std_info->grade);
}

StudentInfo *student_info_copy(const StudentInfo *std_info)
{
	StudentInfo *new_std;

	new_std=g_new(StudentInfo,1);

	new_std->number=std_info->number;
	new_std->name=g_string_new(std_info->name->str);
	new_std->grade=std_info->grade;

	return new_std;
}

void student_info_free(StudentInfo *std_info)
{
	g_string_free(std_info->name,TRUE);

	g_free(std_info);
}

G_DEFINE_BOXED_TYPE(StudentInfo,student_info,student_info_copy,student_info_free);

#define TYPE_STUDENT_INFO (student_info_get_type())

int main()
{
	StudentInfo *stdi;
	StudentInfo *stdi_copy;

	g_type_init();

	stdi=student_info_new(1,"JIm",86.5);
	stdi_copy=g_boxed_copy(TYPE_STUDENT_INFO,stdi);

	student_info_print(stdi_copy);

	g_boxed_free(TYPE_STUDENT_INFO,stdi);
	g_boxed_free(TYPE_STUDENT_INFO,stdi_copy);

	return 0;
}