sec3-类型系统和注册流程

1 类型系统和注册流程

  GObject是一个基类。我们通常不使用GObject本身。因为GObject非常简单,在大多数情况下不足以单独使用。相反,我们使用GObject的子类对象,例如各种GtkWidget。可以说,这种可继承是GObject最重要的特性。本节介绍如何定义GObject的子对象。

2 命令惯例

  本节的一个例子是一个对象表示一个实数。它不是很有用,因为我们已经在C语言中使用了双类型来表示实数。但是,我认为这个例子对于了解定义子对象的技术来说并不是那么糟糕。
  首先,您需要了解命名约定。对象名称由名称空间和名称组成。例如,“GObject”由名称空间“G”和名称“Object”组成。“GtkWidget”由名称空间“Gtk”和名称“Widget”组成。设新对象的名称空间为“T”,名称为“Double”。在本教程中,我们使用“T”作为我们所创建的所有对象的名称空间。
TDouble是对象名称。它是GObject的子对象。它表示一个实数,该数的类型为double。它有一些有用的功能。

3 定义TDoubleClass和TDouble

  当我们说“类型”时,它可以是类型系统中的类型或C语言类型。例如,GObject是类型系统中的类型名称。char, int或double是C语言类型。当“类型”这个词在上下文中的意思很清楚时,我们就叫它“类型”。但如果它是模棱两可的,我们称之为“C类型”或“类型系统中的类型”。
  TDouble对象具有类和实例。类的C类型是TDoubleClass。其结构是这样的:

typedef struct _TDoubleClass TDoubleClass
struct _TDoubleClass {
  GObjectClass parent_class;
}

  TDoubleClass是一个C结构标签名,TDoubleClass是“struct TDoubleClass”。TDoubleClass是一个新创建的C类型。

  • 使用typedef定义类类型。
  • 结构体的第一个成员必须是父类结构。

TDoubleClass不需要自己的成员。
TDouble实例的C类型为TDouble。

typedef struct _TDouble TDouble
struct _TDouble {
  GObject parent;
  double value;
}

这类似于类的结构。

  • 使用typedef定义实例类型。
  • 结构的第一个成员必须是父实例结构。

TDouble有自己的成员value。它是TDouble实例的值。上面的编码惯例需要一直保持。

4 GObject子对象的创建过程

TDouble类型的创建过程与GObject类似。

  1. 用类型系统注册TDouble类型。
  2. 类型系统为TDoubleClass和TDouble分配内存。
  3. 初始化TDoubleClass。
  4. 初始化TDouble。

5 注册

  通常注册是通过宏完成的,比如G DECLARE FINAL TYPE和G DEFINE TYPE。所以你不需要关心注册。但是,在本教程中,理解GObject类型系统很重要,所以我想首先向您展示不带宏的注册。
  有两种类型,静态和动态。静态类型不会销毁它的类,即使所有实例都已销毁。动态类型在销毁最后一个实例时销毁其类。GObject的类型是静态的,它的子类对象的类型也是静态的。函数g_type_register_static注册一个静态对象的类型。下面的代码是从Glib源文件中的gtype.h中提取的。

GType
g_type_register_static (GType           parent_type,
                        const gchar     *type_name,
                        const GTypeInfo *info,
                        GTypeFlags      flags);

上述参数包括:
parent type:父类型。
type name:类型的名称。例如,“TDouble”。
info:类型信息。GTypeInfo结构将在下面解释。GTypeFlags:flag。如果类型是抽象类型或抽象值类型,则设置它们的标志。否则,将其设置为零。
  因为类型系统维持类型的父子类型关系,g_type_refister_static有一个父类型参数。类型系统还保存类型信息。注册后,g_type_refister_static返回新对象的类型。GTypeInfo结构的定义如下:

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:类的大小。例如,TDouble的类大小是sizeof (TDoubleClass)。
  • base init, base finalize:这些函数initialize/finalize类的动态成员。在许多情况下,它们不是必需的,并被赋值为NULL。更多信息,请参见GObject API参考—— GObject API Reference – BaseInitFunc and GObject API Reference – ClassInitFunc.。
  • class init:初始化类的静态成员。将类初始化函数赋给class init。按照约定,名称为__class_init,例如:t_double_class_init。
  • value_table:这通常只对基本类型有用。如果类型是GObject的子类,则赋值NULL。

  这些信息由类型系统保存,并在对象创建或销毁时使用。Class_size和instance_size用于为类和实例分配内存。Class_init和instance_init函数在类或实例初始化时被调用。

Example3.c展示了如何使用g_type_register_static。

 1 #include <glib-object.h>
 2 
 3 #define T_TYPE_DOUBLE  (t_double_get_type ())
 4 
 5 typedef struct _TDouble TDouble;
 6 struct _TDouble {
 7   GObject parent;
 8   double value;
 9 };
10 
11 typedef struct _TDoubleClass TDoubleClass;
12 struct _TDoubleClass {
13   GObjectClass parent_class;
14 };
15 
16 static void
17 t_double_class_init (TDoubleClass *class) {
18 }
19 
20 static void
21 t_double_init (TDouble *d) {
22 }
23 
24 GType
25 t_double_get_type (void) {
26   static GType type = 0;
27   GTypeInfo info;
28 
29   if (type == 0) {
30     info.class_size = sizeof (TDoubleClass);
31     info.base_init = NULL;
32     info.base_finalize = NULL;
33     info.class_init = (GClassInitFunc)  t_double_class_init;
34     info.class_finalize = NULL;
35     info.class_data = NULL;
36     info.instance_size = sizeof (TDouble);
37     info.n_preallocs = 0;
38     info.instance_init = (GInstanceInitFunc)  t_double_init;
39     info.value_table = NULL;
40     type = g_type_register_static (G_TYPE_OBJECT, "TDouble", &info, 0);
41   }
42   return type;
43 }
44 
45 int
46 main (int argc, char **argv) {
47   GType dtype;
48   TDouble *d;
49 
50   dtype = t_double_get_type (); /* or d = T_TYPE_DOUBLE */
51   if (dtype)
52     g_print ("Registration was a success. The type is %lx.\n", dtype);
53   else
54     g_print ("Registration failed.\n");
55 
56   d = g_object_new (T_TYPE_DOUBLE, NULL);
57   if (d)
58     g_print ("Instantiation was a success. The instance address is %p.\n", d);
59   else
60     g_print ("Instantiation failed.\n");
61 
62   return 0;
63 }
64 
  • 16-22:类初始化函数和实例初始化函数。它们在这里什么都不做,但它们是注册所必需的。
  • 24-43: t_double_get_type函数。该函数返回TDouble对象的类型。函数的名称总是<name space>_get_type。宏<NAME_SPACE>_TYPE_<NAME>(所有字符都是大写)将被此函数替换。看第3行。T_TYPE_DOUBLE是一个由t_double_get_type()取代的宏。这个函数有一个静态变量类型(static GType)来保存对象的类型。在第一次调用此函数时,type为0。然后调用g_type_register_static将对象注册到类型系统。在第二次或后续调用时,函数只返回type,因为静态变量type已被g_type_register_static赋值为非零值,并且它保留该值。
  • 30-40:设置info结构并调用g_type_register_static。
  • 45-63:主要功能。获取TDouble对象的类型并显示它。G_object_new用于实例化对象。显示实例的地址。

example3.c在src/misc目录中。
执行结果:

$ cd misc; _build/example3
Registration was a success. The type is 557292894830.
Instantiation was a success. The instance address is 0x557292897000.

6 G_DEFINE_TYPE 宏

上面的注册总是有相同的算法完成的。因此,它可以被定义为一个宏,如G_DEFINE_TYPE。

G_DEFINE_TYPE的作用如下:

  • 声明一个类初始化函数。它的名称是 <name space>_<name>_class_init。例如,如果对象名是TDouble,它就是t_double_class_init。这是一个声明,不是定义。你需要定义它。
  • 声明一个实例初始化函数。其名称为<name space>_<name>_init。例如,如果对象名称为TDouble,则它为t_double_init。这是声明,不是定义。你需要定义它。
  • 定义一个指向父类的静态变量。它的名字是<name space>_<name>_parent_class。例如,如果对象名称是TDouble,它就是t_double_parent_class。
  • 定义一个<name space>_<name>_get_type ()函数。例如,如果对象名称是TDouble,则它是t_double_get_type。注册是在这个函数中完成的,就像前面的小节一样。

使用这个宏可以减少程序的行数。请参阅下面的示例example4.c,其工作原理与example3.c相同。

 1 #include <glib-object.h>
 2 
 3 #define T_TYPE_DOUBLE  (t_double_get_type ())
 4 
 5 typedef struct _TDouble TDouble;
 6 struct _TDouble {
 7   GObject parent;
 8   double value;
 9 };
10 
11 typedef struct _TDoubleClass TDoubleClass;
12 struct _TDoubleClass {
13   GObjectClass parent_class;
14 };
15 
16 G_DEFINE_TYPE (TDouble, t_double, G_TYPE_OBJECT)
17 
18 static void
19 t_double_class_init (TDoubleClass *class) {
20 }
21 
22 static void
23 t_double_init (TDouble *d) {
24 }
25 
26 int
27 main (int argc, char **argv) {
28   GType dtype;
29   TDouble *d;
30 
31   dtype = t_double_get_type (); /* or d = T_TYPE_DOUBLE */
32   if (dtype)
33     g_print ("Registration was a success. The type is %lx.\n", dtype);
34   else
35     g_print ("Registration failed.\n");
36 
37   d = g_object_new (T_TYPE_DOUBLE, NULL);
38   if (d)
39     g_print ("Instantiation was a success. The instance address is %p.\n", d);
40   else
41     g_print ("Instantiation failed.\n");
42 
43   return 0;
44 }
45 

多亏了G_DEFINE_TYPE,我们从编写GTypeInfo和g_type_register_static这样麻烦的代码中解放出来。需要注意的一件重要的事情是遵循init函数的命名约定。
执行结果:

$ cd misc; _build/example4
Registration was a success. The type is 55d4a6d20830.
Instantiation was a success. The instance address is 0x55d4a6d22000.

7 G_DECLARE_FINAL_TYPE 宏

  另一个有用的宏是G_DECLARE_FINAL_TYPE宏。这个宏可以用于最后一种类型(这个类不会再被继承)。最后一种类型是没有任何子类。如果一个类型有子类型,它就是可派生类型。如果你想定义一个可派生类型对象,请使用g_declare_derivatives able_type。然而,在大多数情况下,您可能会编写final类型对象(不会被继承的对象)。

G_DECLARE_FINAL_TYPE执行以下操作:

  • 声明<name space>_<name>_get_type ()函数。这只是声明。你需要定义它。但是你可以使用G_DEFINE_TYPE宏,它的扩展包含了函数的定义。实际上,你不需要自己写定义。
  • 定义typedef struct <_NAME SPACE><NAME>。例如,如果对象名称为TDouble,则typedef struct _TDouble TDouble将包含在展开中。但是你需要在G_DEFINE_TYPE之前自己定义struct _TDouble结构体。
  • 定义<NAME SPACE>_<NAME> 宏。例如,如果对象是TDouble,宏就是T_DOUBLE。它将展开为一个函数,将实参转换为指向对象的指针。例如,T_Double (obj)将obj类型强制转换为TDouble *。
  • 定义<NAME SPACE>_IS_<NAME>宏。例如,如果对象是TDouble,宏就是T_IS_DOUBLE。它将扩展为一个函数,该函数检查参数是否指向TDouble的实例。如果参数指向TDouble的后代,则返回true。
  • 定义了类结构体。一个final类型对象不需要有自己的类结构体成员。定义类似于example4.c中的第11至14行。

你需要在G_DECLARE_FINAL_TYPE之前编写该对象类型的宏定义。例如,如果对象是TDouble,那么

#define T_TYPE_DOUBLE  (t_double_get_type ())

需要在G_DECLARE_FINAL_TYPE之前定义。

example5.c使用这个宏。它像example3.c或example4.c一样工作。

 1 #include <glib-object.h>
 2 
 3 #define T_TYPE_DOUBLE  (t_double_get_type ())
 4 G_DECLARE_FINAL_TYPE (TDouble, t_double, T, DOUBLE, GObject)
 5 
 6 struct _TDouble {
 7   GObject parent;
 8   double value;
 9 };
10 
11 G_DEFINE_TYPE (TDouble, t_double, G_TYPE_OBJECT)
12 
13 static void
14 t_double_class_init (TDoubleClass *class) {
15 }
16 
17 static void
18 t_double_init (TDouble *d) {
19 }
20 
21 int
22 main (int argc, char **argv) {
23   GType dtype;
24   TDouble *d;
25 
26   dtype = t_double_get_type (); /* or d = T_TYPE_DOUBLE */
27   if (dtype)
28     g_print ("Registration was a success. The type is %lx.\n", dtype);
29   else
30     g_print ("Registration failed.\n");
31 
32   d = g_object_new (T_TYPE_DOUBLE, NULL);
33   if (d)
34     g_print ("Instantiation was a success. The instance address is %p.\n", d);
35   else
36     g_print ("Instantiation failed.\n");
37 
38   if (T_IS_DOUBLE (d))
39     g_print ("d is TDouble instance.\n");
40   else
41     g_print ("d is not TDouble instance.\n");
42 
43   if (G_IS_OBJECT (d))
44     g_print ("d is GObject instance.\n");
45   else
46     g_print ("d is not GObject instance.\n");
47 
48   return 0;
49 }
50 

$ cd misc; _build/example5
Registration was a success. The type is 558415d17830.
Instantiation was a success. The instance address is 0x558415d19000.
d is TDouble instance.
d is GObject instance.

8 分文件编写main.c, tdouble.h和tdouble.c

现在是时候将内容分离为三个文件:main.c、tdouble.h和tdouble.c。对象由两个文件定义,头文件和C源文件。

/* filename: tdouble.h */
 1 #ifndef __T_DOUBLE_H__
 2 #define __T_DOUBLE_H__
 3 
 4 #include <glib-object.h>
 5 
 6 #define T_TYPE_DOUBLE  (t_double_get_type ())
 7 G_DECLARE_FINAL_TYPE (TDouble, t_double, T, DOUBLE, GObject)
 8 
 9 gboolean
10 t_double_get_value (TDouble *d, double *value);
11 
12 void
13 t_double_set_value (TDouble *d, double value);
14 
15 TDouble *
16 t_double_new (double value);
17 
18 #endif /* __T_DOUBLE_H__ */
19 
  • 头文件的内容是公共的,即它对任何文件都是开放的。头文件包括宏,宏提供类型信息、强制转换和类型检查以及公共函数。
  • 1,2,18:这些指令阻止编译器读取头文件两次或更多次。如果你的编译器支持#pragma once指令,你可以用它代替。它没有正式定义,但在许多编译器中得到广泛支持。
  • 6,7: T_TYPE_DOUBLE是公共的。G_DECLARE_FINAL_TYPE也扩展为公共定义。
  • 9-13:函数声明。它们是对象值的getter和setter方法。
  • 15-16:对象实例化函数。
/* filename: tdouble.c */
 1 #include "tdouble.h"
 2 
 3 struct _TDouble {
 4   GObject parent;
 5   double value;
 6 };
 7 
 8 G_DEFINE_TYPE (TDouble, t_double, G_TYPE_OBJECT)
 9 
10 static void
11 t_double_class_init (TDoubleClass *class) {
12 }
13 
14 static void
15 t_double_init (TDouble *d) {
16 }
17 
18 gboolean
19 t_double_get_value (TDouble *d, double *value) {
20   g_return_val_if_fail (T_IS_DOUBLE (d), FALSE);
21 
22   *value = d->value;
23   return TRUE;
24 }
25 
26 void
27 t_double_set_value (TDouble *d, double value) {
28   g_return_if_fail (T_IS_DOUBLE (d));
29 
30   d->value = value;
31 }
32 
33 TDouble *
34 t_double_new (double value) {
35   TDouble *d;
36 
37   d = g_object_new (T_TYPE_DOUBLE, NULL);
38   d->value = value;
39   return d;
40 }
41 
  • 3-6:实例结构的声明。由于G_DECLARE_FINAL_TYPE宏发出类型器结构体_TDouble TDouble,结构的标记名必须为_TDouble。
  • 8: G_DEFINE_TYPE宏。
  • 10-16:类和实例初始化函数。目前,他们什么都不做。
  • 18 - 24: Getter。实参值是指向double类型变量的指针。将对象值(d->值)赋给变量。如果成功,则返回TRUE。G_return_val_if_fail用于检查参数类型。如果参数d不是TDouble类型,它将错误输出到日志并立即返回FALSE。此函数用于报告程序员的错误。您不应该将它用于运行时错误。更多信息请参见Glib API参考—错误报告。G_return_val_if_fail不在静态类函数中使用,它们是私有的,因为静态函数只能从同一文件中的函数调用。这样的函数很清楚参数类型。g_return_val_if_fail用于公共函数。
  • 26-31: Setter。G_return_if_fail函数用于检查参数类型。这个函数不返回任何值。因为t_double_set_value的类型是void,所以不会返回任何值。因此,我们使用g_return_if_fail而不是g_return_val_if_fail。
  • 33-40:对象实例化函数。它有一个参数值来设置对象的值。
  • 37:该函数使用g_object_new实例化对象。参数T_TYPE_DOUBLE扩展为函数t_double_get_type()。如果这是对t_double_get_type的第一次调用,则将执行类型注册。
/* filename: main.c */
 1 #include <glib-object.h>
 2 #include "tdouble.h"
 3 
 4 int
 5 main (int argc, char **argv) {
 6   TDouble *d;
 7   double value;
 8 
 9   d = t_double_new (10.0);
10   if (t_double_get_value (d, &value))
11     g_print ("t_double_get_value succesfully assigned %lf to value.\n", value);
12   else
13     g_print ("t_double_get_value failed.\n");
14 
15   t_double_set_value (d, -20.0);
16   g_print ("Now, set d (tDouble object) with %lf.\n", -20.0);
17   if (t_double_get_value (d, &value))
18     g_print ("t_double_get_value succesfully assigned %lf to value.\n", value);
19   else
20     g_print ("t_double_get_value failed.\n");
21 
22   return 0;
23 }
24 
  • 2:包含tdouble.h。这对于访问TDouble对象是必要的。
  • 9:实例化TDouble对象,并设置d指向对象。
  • 10-13:测试对象的getter。
  • 15-20:测试对象的setter。
    源文件位于src/ tdoube1中。将当前目录更改为上面的目录并输入以下内容。
meson _build
ninja -C _build

$ cd tdouble1; _build/example6
t_double_get_value succesfully assigned 10.000000 to value.
Now, set d (tDouble object) with -20.000000.
t_double_get_value succesfully assigned -20.000000 to value.

这个例子非常简单。但是任何对象都有头文件和C源文件,就像上面的例子一样。他们遵循惯例。你可能知道会议的重要性。欲了解更多信息,请参阅GObject API参考—约定。

9 函数

对象的函数对其他对象开放。它们就像面向对象语言中的公共方法。

向TDouble对象添加计算操作符是很自然的,因为它们表示实数。例如,t_double_add将实例的值与另一个实例相加。然后它创建一个新的TDouble实例,该实例的值是这些值的和。

TDouble *
t_double_add (TDouble *self, TDouble *other) {
  g_return_val_if_fail (T_IS_DOUBLE (self), NULL);
  g_return_val_if_fail (T_IS_DOUBLE (other), NULL);
  double value;

  if (! t_double_get_value (other, &value))
    return NULL;
  return t_double_new (self->value + value);
}

self是函数所属的实例。other是另一个TDouble实例。

self的值可以通过self->值访问,但不能使用other->值获取other的值。使用函数t_double_get_value代替。因为self是来自于other的一个实例。一般来说,对象的结构对其他对象是不开放的。当一个对象A访问另一个对象B时,A必须使用B提供的公共函数。

10 练习

编写TDouble对象的减、乘、除和符号改变(一元减)函数。比较你的程序与src/tdouble2目录下的tdouble.c。

翻译自GObject tutorial

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值