GObject学习笔记(二)类型创建与注册

paw5zx Lv4

前言

上一节中我们介绍了GObject类型的类和实例变量的创建和使用。GObject是一个基本的可实例化类类型,是所有使用GObject系统的类型的基类,提供了继承、封装、多态等面向对象的核心特性。不过我们一般不直接使用GObject本身,而是通过继承GObject来创建新的类型。

对于GObject官方文档中:

可实例化的类类型:object(Instantiatable classed types: objects),本文以及后文中称其为类型。
不可实例化的类类型:interface(Non-instantiatable classed types: interfaces),本文以及后文中称其为接口。

本文中我们主要介绍一下GObject类型系统中的命名约定,以及介绍如何创建一个自定义的类型。

命名约定

命名约定贯穿整个GObject类型系统。

首先,我们先了解基本的命名约定:一个对象名由命名空间(也可称为moudle)和名称组成。例如,GObject由命名空间“G”和名称“Object”组成。GtkWidget由命名空间“Gtk”和名称“Widget”组成。在本文中,为了更好的演示,我们将定义一个新类型,其命名空间为“Paw”,名称为“Double”,用于表示一个浮点数据。

好了,你现在已了解了基本的命名约定,下面我们来看一下更多的命名约定:

  • 类型名称必须至少有三个字符长,并以a-z, A-Z或_开头。
  • 函数名使用object_method模式:要在类型为Double的实例上定义名为add的函数,则函数名为double_save
  • 使用前缀可以避免与其他项目的名称空间冲突。如我的库(或应用程序)名为Paw,就在所有函数名前加上paw_。例如:paw_double_add。前缀应该是一个词,即在第一个字母之后不应该包含任何大写字母。例如,应该是Exampleprefix而不是ExamplePrefix。

除了上述约定,本文还将介绍其他命名约定,将在文中适时描述。

类型创建和注册

类型的创建是指定义一个新的GObject类型,包括其所有的数据和行为。这会涉及到:

  • 定义类结构体和实例结构体
  • 定义类型相关的函数:如类初始化(class_init)、实例初始化(instance_init)等函数。

类型的注册是将创建的新GObject类型在GObject类型系统中注册,使其成为GObject系统可识别和使用的一部分。这会涉及到:

  • 调用类型注册函数:g_type_register_staticg_type_register_dynamic,这些函数负责在GObject类型系统中注册新创建的类型

在我们平时的使用中,类型的创建和注册通常可以通过GObject提供的一些便利宏来完成,如G_DECLARE_FINAL_TYPEG_DEFINE_TYPE等。这些便利宏可以让用户不用关心一些类型创建和注册的具体细节。

在本文中,为了更好地理解GObject类型系统,会先展示不使用便利宏的类型创建和注册过程,之后再介绍便利宏。

GObject类型系统中有两种类型:静态类型和动态类型。

对于静态类型,即使所有实例都被销毁后也不会销毁其类。对于动态类型,在最后一个实例被销毁时销毁其类。GObject的类型是静态的,其派生的类型也是静态的。

手动

定义类型的类和实例结构体

类和实例结构体命名约定如下:

  • 类结构体的命名为:<Moudle><Name>Class,如PawDoubleClass。
  • 实例结构体的命名为:<Moudle><Name>,如PawDouble。
1
2
3
4
5
6
//类结构体
typedef struct _PawDoubleClass PawDoubleClass;
struct _PawDoubleClass
{
GObjectClass parent_class;
};

PawDoubleClass的第一个成员必须是其父类型的类结构体

1
2
3
4
5
6
7
//实例结构体
typedef struct _PawDouble PawDouble;
struct _PawDouble
{
GObject parent;
double value;
};

PawDouble的第一个成员必须是其父类型的实例结构体。
PawDouble有自己的成员value,是PawDouble类型代表的浮点数据的值

定义初始化函数

类和实例的初始化函数命名约定如下:

  • 类初始化函数的命名为:<moudle>_<name>_class_init,如paw_double_class_init。
  • 实例初始化函数的命名为:<moudle>_<name>_init,如paw_double_init。
1
2
3
4
5
6
7
8
9
//类构造函数
static void paw_double_class_init(PawDoubleClass* class)
{
}

//实例构造函数
static void paw_double_init(PawDouble* self)
{
}

类型注册

对于静态类型,我们使用g_type_register_static将其注册至GObject的类型系统中

1
2
3
4
5
6
//file: gtype.h
GType
g_type_register_static (GType parent_type,
const gchar* type_name,
const GTypeInfo* info,
GTypeFlags flags);

其中:

  • parent_type:父类类型
  • type_name:类型名称,如PawDouble
  • info:向类型系统传递类型初始化和销毁的相关信息。GTypeInfo结构体将在下文中介绍
  • flags:如果类型是抽象类型或抽象值类型,则设置它们的标志位。否则,将其设为0。

GTyepInfo结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef struct _GTypeInfo  GTypeInfo;

struct _GTypeInfo
{
/* interface types, classed types, instantiated types */
guint16 class_size;

GBaseInitFunc base_init;
GBaseFinalizeFunc base_finalize;

/* interface types, classed types, instantiated types */
GClassInitFunc class_init;
GClassFinalizeFunc class_finalize;
gconstpointer class_data;

/* instantiated types */
guint16 instance_size;
guint16 n_preallocs;
GInstanceInitFunc instance_init;

/* value handling */
const GTypeValueTable *value_table;
};

此结构必须在类型注册前创建,其中:

  • class_size: 类的大小。例如,PawDouble类型的类大小为sizeof(PawDoubleClass)
  • base_init, base_finalize: 这些函数初始化/销毁类的动态成员。在许多情况下,它们不是必需的,被赋值为NULL。详见GObject.BaseInitFunc GObject.ClassInitFunc
  • class_init: 类的初始化函数。用户需要将定义的类初始化函数赋值给class_init成员。按照命名约定,类初始化函数名称为<moudle>_<name>_class_init,例如paw_double_class_init
  • class_finalize: 类的销毁函数。因为GObject的子类类型是静态的,所以它没有销毁函数。将class_finalize成员赋值为NULL即可。
  • class_data: 用户提供数据,传递给类的初始化/销毁函数。通常赋值为 NULL。
  • instance_size: 实例的大小。例如,PawDouble类型的实例大小为sizeof(PawDouble)
  • n_preallocs: 用于指定预分配的实例数量。自GLib 2.10以来,该字段被忽略。
  • instance_init: 实例的初始化函数。用户需要将定义的实例初始化函数赋值给instance_init成员。按照命名约定,实例初始化函数名称为<moudle>_<name>_init,例如 paw_double_init
  • value_table: 这通常只对基本类型有用。如果类型是GObject的后代,赋值为NULL。

我们将编写一个函数paw_double_get_type,用于向GObject类型系统注册新类型,并返回所注册类型的id

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GType paw_double_get_type(void) 
{
static GType type = 0;
GTypeInfo info;

if(type == 0)
{
//赋值
info.class_size = sizeof(PawDoubleClass);
info.base_init = NULL;
info.base_finalize = NULL;
info.class_init = (GClassInitFunc) paw_double_class_init;
info.class_finalize = NULL;
info.class_data = NULL;
info.instance_size = sizeof(PawDouble);
info.n_preallocs = 0;
info.instance_init = (GInstanceInitFunc) paw_double_init;
info.value_table = NULL;
//注册类型
type = g_type_register_static(G_TYPE_OBJECT, "PawDouble", &info, 0);
}
return type;
}

汇总

上述的类型创建和注册过程汇总如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <glib-object.h>

#define PAW_TYPE_DOUBLE (paw_double_get_type())

//定义实例结构体
typedef struct _PawDouble PawDouble;
struct _PawDouble
{
GObject parent;
double value;
};

//定义类结构体
typedef struct _PawDoubleClass PawDoubleClass;
struct _PawDoubleClass
{
GObjectClass parent_class;
};

//类构造函数
static void paw_double_class_init(PawDoubleClass* class)
{
}

//实例构造函数
static void paw_double_init(PawDouble* self)
{
}

//首次调用时注册类型
//返回类型
GType paw_double_get_type(void)
{
static GType type = 0;
GTypeInfo info;

if(type == 0)
{
info.class_size = sizeof(PawDoubleClass);
info.base_init = NULL;
info.base_finalize = NULL;
info.class_init = (GClassInitFunc) paw_double_class_init;
info.class_finalize = NULL;
info.class_data = NULL;
info.instance_size = sizeof(PawDouble);
info.n_preallocs = 0;
info.instance_init = (GInstanceInitFunc) paw_double_init;
info.value_table = NULL;
type = g_type_register_static(G_TYPE_OBJECT, "PawDouble", &info, 0);
}
return type;
}

int main(int argc, char **argv)
{
GType dtype;
PawDouble* d;

dtype = paw_double_get_type(); /* or dtype = PAW_TYPE_DOUBLE */
if(dtype)
g_print("Registration was a success. The type is %lx.\n", dtype);
else
g_print("Registration failed.\n");

d = g_object_new(PAW_TYPE_DOUBLE, NULL);
if(d)
g_print("Instantiation was a success. The instance address is %p.\n", d);
else
g_print("Instantiation failed.\n");
g_object_unref(d); /* Releases the object d. */

return 0;
}
  • 20-28:类初始化函数和实例初始化函数,参数class是指类结构体,参数self是指实例结构体。这两个函数在示例中内容为空,但他们是注册过程必须的。

  • 30-52:paw_double_get_type函数,返回PawDouble类型的id。这种函数的命名总是遵循<moudle>_<name>_get_type。同时,宏<MOUDLE>_TYPE_<NAME>(所有字符都是大写)是这个函数的别名。paw_double_get_type函数有一个静态变量type来存储对象的类型。在此函数的第一次调用时,type是零。然后函数内调用 g_type_register_static将新类型注册到类型系统中。在第二次和之后的调用中,该函数只是返回type,因为静态变量type已经被g_type_register_static赋予了非零值,并且它保持该值。

  • 39-49:设置info结构体并调用g_type_register_static注册。

  • 54-73:主函数。获取PawDouble类型id并显示它。函数g_object_new被用来创建类型实例。GObject API文档中表示g_object_new返回一个指向GObject实例的指针,但实际上它返回的是一个gpointergpointer基本等同于void*,可以被赋值给指向任何类型的指针。因此,语句d = g_object_new(PAW_TYPE_DOUBLE, NULL);是正确的。如果函数g_object_new返回的是GObject*,那么就需要对返回的指针进行类型转换。在创建完成后,我们打印了实例的地址。最后,使用函数g_object_unref释放并销毁实例。

便利宏

注册的过程总是可以使用相同的算法完成,因此可以被定义为宏。

GObject提供了一些宏:

G_DEFINE_TYPE

1
#define G_DEFINE_TYPE(TN, t_n, T_P)

其中:

  • TN:新类型的名称,驼峰形式,如PawDouble
  • t_n:新类型的名称,全小写,单词之间使用下划线分隔。如paw_double
  • T_P:父类的类型,GType

G_DEFINE_TYPE内部完成了以下操作:

  • 声明一个类初始化函数。其名称为<moudle>_<name>_class_init。例如,如果类型名称是PawDouble,它就是paw_double_class_init。这只是一个声明,不是定义。用户需要定义它。
  • 声明一个实例初始化函数。其名称为<moudle>_<name>_init。例如,如果类型名称是PawDouble,它就是paw_double_init。这只是一个声明,不是定义。用户需要定义它。
  • 声明一个指向父类的静态变量。其名称为<moudle>_<name>_parent_class。例如,如果对象名称是PawDouble,它就是paw_double_parent_class
  • 定义一个<moudle>_<name>_get_type()函数。例如,如果对象名称是PawDouble,它就是paw_double_get_type。在这个函数中完成类型注册,就像前面描述的那样。

要使用此宏完成类型的创建和注册,我们只需要再手动提供

  • 类和实例结构体的定义(必须在使用G_DEFINE_TYPE之前完成)
  • 类和实例初始化函数的定义(通常在使用G_DEFINE_TYPE之后完成)

因此,我们可以将手动创建和注册类型的代码简化如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <glib-object.h>

#define PAW_TYPE_DOUBLE (paw_double_get_type())

typedef struct _PawDouble PawDouble;
struct _PawDouble
{
GObject parent;
double value;
};

typedef struct _PawDoubleClass PawDoubleClass;
struct _PawDoubleClass
{
GObjectClass parent_class;
};

G_DEFINE_TYPE(PawDouble, paw_double, G_TYPE_OBJECT)

static void paw_double_class_init(PawDoubleClass* class)
{
}

static void paw_double_init(PawDouble* self)
{
}

int main(int argc, char **argv)
{
GType dtype;
PawDouble* d;

dtype = paw_double_get_type(); /* or dtype = PAW_TYPE_DOUBLE */
if(dtype)
g_print("Registration was a success. The type is %lx.\n", dtype);
else
g_print("Registration failed.\n");

d = g_object_new(PAW_TYPE_DOUBLE, NULL);
if(d)
g_print("Instantiation was a success. The instance address is %p.\n", d);
else
g_print("Instantiation failed.\n");
g_object_unref(d);

return 0;
}

G_DECLARE_FINAL_TYPE

另一个有用的宏是G_DECLARE_FINAL_TYPE。这个宏可以用于声明最派生类型(final type)。如果想声明一个可派生类型,可使用G_DECLARE_DERIVABLE_TYPE(会在后面介绍)。然而,在大多数情况下,我们都是需要创建最派生类型。

其声明为:

1
#define G_DECLARE_FINAL_TYPE(ModuleObjName, module_obj_name, MODULE, OBJ_NAME, ParentName)

其中:

  • ModuleObjName:新类型的名称,驼峰形式(如PawDouble
  • module_obj_name:新类型名称,全小写,单词之间使用下划线分隔(如paw_double
  • MODULE:模块的名称,全大写(如PAW
  • OBJ_NAME:新类型的名称,去除修饰,全大写如(DOUBLE
  • ParentName:父类型的名称,驼峰形式(如GObject

G_DECLARE_FINAL_TYPE内部完成以下操作:

  • 声明返回类型为GType<moudle>_<name>_get_type()函数。这只是声明,用户需要定义它。但其实可以使用G_DEFINE_TYPE,其扩展包括该函数的定义。所以,实际上用户不需要手动编写定义。
  • 定义<Moudle><Name>,简化实例结构体的使用,即typedef struct _<Moudle><Name> <Moudle><Name>。例如,如果类型名称是PawDouble,那么内部有typedef struct _PawDouble PawDouble。但是_PawDouble结构体本身并没有在宏中定义,用户应该在使用G_DEFINE_TYPE()之前定义。
  • 定义类结构体_<Moudle><Name>Class。由于不派生其他类型,因此类结构除了父类型的类结构体以外没有其他成员。然后typedef struct _<Moudle><Name>Class <Moudle><Name>Class
  • 定义<MOUDLE>_<NAME>()静态内联函数。例如,如果类型是PawDouble,则函数是PAW_DOUBLE()。它将参数转换为指向该类型实例的指针。例如,PAW_DOUBLE(obj)obj的类型转换为PawDouble*
  • 定义<MOUDLE>_IS_<NAME>()函数。例如,如果对象是PawDouble,则函数是PAW_IS_DOUBLE()。它会检查参数是否为指向PawDouble类型实例的指针。如果参数指向PawDouble及其后代,则宏返回真。

在使用G_DECLARE_FINAL_TYPE之前,我们还要手动定义<MOUDLE>_TYPE_<NAME>,如

1
#define PAW_TYPE_DOUBLE paw_double_get_type

知道了G_DECLARE_FINAL_TYPE的使用方法,我们就可以将声明和实现分离了

声明和实现分离

现在将上面的示例拆分到PawDouble.cPawDouble.hmain.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//file: PawDouble.h
#ifndef PAW_DOUBLE_H
#define PAW_DOUBLE_H

#include <glib-object.h>
G_BEGIN_DECLS

#define PAW_TYPE_DOUBLE (paw_double_get_type())
G_DECLARE_FINAL_TYPE(PawDouble, paw_double, PAW, DOUBLE, GObject)

gboolean paw_double_get_value(PawDouble* self, double* value);

void paw_double_set_value(PawDouble* self, double value);

PawDouble* paw_double_new(double value);

G_END_DECLS

#endif /* PAW_DOUBLE_H */
  • 头文件是公开的的,即对任何文件都是开放的。
  • 2-3:预处理器指令,用于防止头文件的内容被多次包含。
  • 6,17:GObject库中的宏,用于确保当GObject库的头文件被C++编译器包含时,内部声明的C函数不会被当作C++函数进行名称修饰,从而可以在C++代码中正常调用C函数。
  • 8-9:PAW_TYPE_DOUBLE是公开的。G_DECLARE_FINAL_TYPE展开为公开定义。
  • 11-13:公共函数声明。它们是对象值的获取器和设置器,被称为“实例方法”,这是面向对象语言中使用的。
  • 15:对象实例化函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//file: PawDouble.c
#include "PawDouble.h"

struct _PawDouble
{
GObject parent;
double value;
};

G_DEFINE_TYPE(PawDouble, paw_double, G_TYPE_OBJECT)

static void paw_double_class_init(PawDoubleClass* class)
{
}

static void paw_double_init(PawDouble* self)
{
}

gboolean paw_double_get_value(PawDouble* self, double* value)
{
g_return_val_if_fail(PAW_IS_DOUBLE(self), FALSE);

*value = self->value;
return TRUE;
}

void paw_double_set_value(PawDouble* self, double value)
{
g_return_if_fail(PAW_IS_DOUBLE(self));

self->value = value;
}

PawDouble* paw_double_new(double value)
{
PawDouble* d;

d = g_object_new(PAW_TYPE_DOUBLE, NULL);
d->value = value;
return d;
}
  • 4-8:实例结构的声明。由于G_DECLARE_FINAL_TYPE宏定义了typedef struct _PawDouble PawDouble,因此结构的标签名必须是_PawDouble
  • 10:G_DEFINE_TYPE 宏。
  • 12-18:类和实例初始化函数。目前,它们不执行任何操作。
  • 20-26:获取器。参数值是指向双精度类型变量的指针。将对象值(self->value)赋给该变量。如果成功,则返回TRUE。函数g_return_val_if_fail用于检查参数类型。如果参数self不是PawDouble类型,它会输出错误到日志并立即返回FALSE。在静态类函数中不需要使用g_return_val_if_fail,因为静态函数的作用域局限于定义它们的同一源文件内,且通常由同一文件内的其他函数调用,因此调用者对于需要传递给静态函数的参数类型已经有了充分的了解。在这种情况下,使用g_return_val_if_fail进行参数有效性检查可能是多余的(待验证)。
  • 28-33:设置器。这个函数不返回任何值。因此,我们使用g_return_if_fail而不是g_return_val_if_fail
  • 35-42:实例化函数。它的参数值用来设置实例表示的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//file: main.c
#include <glib-object.h>
#include "PawDouble.h"

int main(int argc, char **argv)
{
PawDouble* d;
double value;

d = paw_double_new(10.0);
if(paw_double_get_value(d, &value))
g_print("paw_double_get_value succesfully assigned %lf to value.\n", value);
else
g_print("paw_double_get_value failed.\n");

paw_double_set_value(d, -20.0);
g_print("Now, set d(PawDouble object) with %lf.\n", -20.0);
if(paw_double_get_value(d, &value))
g_print("paw_double_get_value succesfully assigned %lf to value.\n", value);
else
g_print("paw_double_get_value failed.\n");
g_object_unref(d);

return 0;
}
  • 3:包含PawDouble.h。这是获取PawDouble类型的必要步骤
  • 9:实例化PawDouble类型实例并使用指针d指向对象
  • 11-14:测试获取器
  • 16-21:测试设置器
  • 22:释放d

在实际的使用中,任何一个类型都会有这样一个头文件和对应的源文件。

参考文章

1.GObject Tutorial for beginners

  • 标题: GObject学习笔记(二)类型创建与注册
  • 作者: paw5zx
  • 创建于 : 2024-11-05 23:23:41
  • 更新于 : 2024-11-14 20:48:23
  • 链接: https://paw5zx.github.io/GObject-tutorial-beginner-02/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论