GObject学习教程---第十二章:GObject 信号机制——信号注册

官网API:https://developer.gnome.org/gobject/stable/

本文是学习学习他人的博客的心得(具体详见“楼主见解”),如果源网站可访问的话,建议直接访问源网站:

楼主见解:

主要讲解信号的流程,不想了解流程,不看也罢。

主要讲解g_signal_new函数的使用,以及具体参数的意义

原型:

guint g_signal_new (const gchar        *signal_name,
                    GType               itype,
                    GSignalFlags        signal_flags,
                    guint               class_offset,
                    GSignalAccumulator  accumulator,
                    gpointer            accu_data,
                    GSignalCMarshaller  c_marshaller,
                    GType               return_type,
                    guint               n_params,
                    ...);

’调用:

  g_signal_new ("hello",
                      G_TYPE_FROM_CLASS (klass),
                      G_SIGNAL_RUN_FIRST,
                      G_STRUCT_OFFSET (SignalDemoClass, default_handler),
                      NULL,
                      NULL,
                      g_cclosure_marshal_VOID__STRING,
                      G_TYPE_NONE,
                      1, 
                      G_TYPE_STRING);

说明:

  • 第 1 个参数是字符串“hello”,它表示信号。
  • 第 2 个参数是 SignalDemo 类的类型 ID,可以使用 G_TYPE_FROM_CLASS 宏从 SignalDemoClass 结构体中获取,也可直接使用 signal-demo.h 中定义的宏 SIGNAL_TYPE_DEMO。
  • 第 3 个参数可暂时略过。
  • 第 4 个参数比较关键,它是一个内存偏移量,主要用于从 SignalDemoClass 结构体中找到 default_handler 指针的位置,可以使用 G_STRUCT_OFFSET 宏来获取,也可以直接根据 signal-demo.h 中的 SignalDemoClass 结构体的定义,使用 sizeof (GObjectClass) 来得到内存偏移量,因为 default_handler 指针之前只有一个 GObjectClass 结构体成员。
  • 第 5 个和第 6 个参数暂时略过。
  • 第 7 个参数设定闭包的  marshal。在文档“函数指针、回调函数与 GObject 闭包” 中,描述了 GObject 的闭包的概念与结构,我们可以将它视为回调函数 + 上下文环境而构成的一种数据结构,或者再简单一点,将其视为回调函数。另外,在那篇文档中,我们也对 marshal 的概念进行了一些粗浅的解释。事实上 marshal 主要是用来“翻译”闭包的参数和返回值类型的,它将翻译的结果传递给闭包。之所以不直接调用闭包,而是在其外加了一层 marshal 的包装,主要是方便 GObject 库与其他语言的绑定。例如,我们可以写一个 pyg_closure_marshal_VOID__STRING 函数,其中可以调用 python 语言编写的“闭包”并将其计算结果传递给 GValue 容器,然后再从 GValue 容器中提取计算结果。
  • 第 8 个参数指定 marshal 函数的返回值类型。由于本例的第 7 个参数所指定的 marshal 是 g_cclosure_marshal_VOID__STRING 函数的返回值是 void,而 void 类型在 GObject 库的类型管理系统是 G_TYPE_NONE 类型。
  • 第 9 个参数指定 g_signal_new 函数向 marshal 函数传递的参数个数,由于本例使用的 marshal 函数是 g_cclosure_marshal_VOID__STRING 函数,g_signal_new 函数只向其传递 1 个参数。
  • 第 10 个参数是可变参数,其数量由第 8 个参数决定,用于指定 g_signal_new 函数向 marshal 函数传递的参数类型。由于本例使用的 marshal 函数是 g_cclosure_marshal_VOID__STRING 函数,并且 g_signal_new 函数只向其传递一个参数,所以传入的参数类型为 G_TYPE_STRING(GObject 库类型管理系统中的字符串类型)。

源博客网址:http://garfileo.is-programmer.com/categories/6934/posts

GObject 信号机制——信号注册

上一篇文档“GObject 的信号机制”只是挖了一个坑便结束了,本篇尝试填坑,不过也不敢有所保证。因为我也不确定会不会因为被 GObject 的信号内幕再次搞晕。

我们先老老实实的阅读 GObject 参考手册的“Concepts / Signal”部分,尽量多获得一些面上的认识。手册中最关键的一句话是:每一个信号在注册的时候都要与某种数据类型(包括 GObject 库中的内建类型或 GObject 子类类型)存在关联,这种数据类型的使用者需要实现信号与闭包的连接,这样在信号被发射时,闭包会被调用。这句话,意味着我们要使用 GObject 信号机制,那么就必须要完成两个步骤:第一个步骤是信号注册,主要解决信号与数据类型的关联问题;第二个步骤是信号连接,主要处理信号与闭包的连接问题。本文主要考察信号注册的大致过程。

信号可以与 GObject 库的类型管理机制中“可实例化”的数据类型进行关联,但是 GObject 参考手册建议我们最好是只在 GObject 子类类型中使用信号,因为信号跟类/对象在逻辑上比较相符。

有 三个函数可以实现信号注册,即 g_signal_newv、g_signal_new_valist 以及 g_signal_new,其中 g_signal_new_valist 与 g_signal_new 函数皆基于 g_signal_newv 函数实现,但是 g_signal_new 函数的名字看上去最平易近人。我们不理睬它们内部是如何实现的,只需要理解它们所接受的参数的含义即可。所以,我们可以从分析 g_signal_new 函数的参数来理解有关信号注册的一些概念。

g_signal_new 函数的声明如下:

1

2

3

4

5

6

7

8

9

10

guint g_signal_new (const gchar        *signal_name,

                    GType               itype,

                    GSignalFlags        signal_flags,

                    guint               class_offset,

                    GSignalAccumulator  accumulator,

                    gpointer            accu_data,

                    GSignalCMarshaller  c_marshaller,

                    GType               return_type,

                    guint               n_params,

                    ...);

g_signal_new 函数的参数较多,其中每个参数多少都有点深不可测的背景,所以直接理解是非常困难的。我们需要构建实例,从而获得最直观的理解。

首先,我们定义一个 GObject 的子类——SignalDemo 类,其头文件 signal-demo.h 内容如下:

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

#ifndef SIGNAL_DEMO_H

#define SIGNAL_DEMO_H

  

#include <glib-object.h>

  

#define SIGNAL_TYPE_DEMO (signal_demo_get_type ())

#define SIGNAL_DEMO(object) \

        G_TYPE_CHECK_INSTANCE_CAST ((object), SIGNAL_TYPE_DEMO, SignalDemo)

#define SIGNAL_IS_DEMO(object) \

        G_TYPE_CHECK_INSTANCE_TYPE ((object), SIGNAL_TYPE_DEMO))

#define SIGNAL_DEMO_CLASS(klass) \

        (G_TYPE_CHECK_CLASS_CAST ((klass), SIGNAL_TYPE_DEMO, SignalDemoClass))

#define SIGNAL_IS_DEMO_CLASS(klass) \

        (G_TYPE_CHECK_CLASS_TYPE ((klass), SIGNAL_TYPE_DEMO))

#define SIGNAL_DEMO_GET_CLASS(object) (\

                G_TYPE_INSTANCE_GET_CLASS ((object), SIGNAL_TYPE_DEMO, SignalDemoClass))

  

typedef struct _SignalDemo SignalDemo;

struct _SignalDemo {

        GObject parent;

};

  

typedef struct _SignalDemoClass SignalDemoClass;

struct _SignalDemoClass {

        GObjectClass parent_class;

        void (*default_handler) (gpointer instance, const gchar *buffer, gpointer userdata);

};

  

GType signal_demo_get_type (void);

#endif

SignalDemo 类的源文件 signal-demo.c 内容如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

#include "signal-demo.h"

G_DEFINE_TYPE (SignalDemo, signal_demo, G_TYPE_OBJECT);

static void

signal_demo_default_handler (gpointer instance, const gchar *buffer, gpointer userdata)

{

        g_printf ("Default handler said: %s\n", buffer);

}

void

signal_demo_init (SignalDemo *self)

{

}

void

signal_demo_class_init (SignalDemoClass *klass)

{

        klass->default_handler = signal_demo_default_handler;

}

基于此前所写的 GObject 学习笔记系列,上述代码不难理解,无非就是定义了一个 SignalDemo 类,其类结构体中包含了一个函数指针 default_handler,并在类结构体初始化函数中使该指针指向函数 signal_demo_default_handler。

下面我们开始为 SignalDemo 类注册一个“hello”信号,只需修改一下 SignalDemo 类的类结构体初始化函数,即:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

void

signal_demo_class_init (SignalDemoClass *klass)

{

        klass->default_handler = signal_demo_default_handler;

        g_signal_new ("hello",

                      G_TYPE_FROM_CLASS (klass),

                      G_SIGNAL_RUN_FIRST,

                      G_STRUCT_OFFSET (SignalDemoClass, default_handler),

                      NULL,

                      NULL,

                      g_cclosure_marshal_VOID__STRING,

                      G_TYPE_NONE,

                      1,

                      G_TYPE_STRING);

}

此时,观察一下 g_signal_new 函数的参数:

  • 第 1 个参数是字符串“hello”,它表示信号。
  • 第 2 个参数是 SignalDemo 类的类型 ID,可以使用 G_TYPE_FROM_CLASS 宏从 SignalDemoClass 结构体中获取,也可直接使用 signal-demo.h 中定义的宏 SIGNAL_TYPE_DEMO。
  • 第 3 个参数可暂时略过。
  • 第 4 个参数比较关键,它是一个内存偏移量,主要用于从 SignalDemoClass 结构体中找到 default_handler 指针的位置,可以使用 G_STRUCT_OFFSET 宏来获取,也可以直接根据 signal-demo.h 中的 SignalDemoClass 结构体的定义,使用 sizeof (GObjectClass) 来得到内存偏移量,因为 default_handler 指针之前只有一个 GObjectClass 结构体成员。
  • 第 5 个和第 6 个参数暂时略过。
  • 第 7 个参数设定闭包的  marshal。在文档“函数指针、回调函数与 GObject 闭包” 中,描述了 GObject 的闭包的概念与结构,我们可以将它视为回调函数 + 上下文环境而构成的一种数据结构,或者再简单一点,将其视为回调函数。另外,在那篇文档中,我们也对 marshal 的概念进行了一些粗浅的解释。事实上 marshal 主要是用来“翻译”闭包的参数和返回值类型的,它将翻译的结果传递给闭包。之所以不直接调用闭包,而是在其外加了一层 marshal 的包装,主要是方便 GObject 库与其他语言的绑定。例如,我们可以写一个 pyg_closure_marshal_VOID__STRING 函数,其中可以调用 python 语言编写的“闭包”并将其计算结果传递给 GValue 容器,然后再从 GValue 容器中提取计算结果。
  • 第 8 个参数指定 marshal 函数的返回值类型。由于本例的第 7 个参数所指定的 marshal 是 g_cclosure_marshal_VOID__STRING 函数的返回值是 void,而 void 类型在 GObject 库的类型管理系统是 G_TYPE_NONE 类型。
  • 第 9 个参数指定 g_signal_new 函数向 marshal 函数传递的参数个数,由于本例使用的 marshal 函数是 g_cclosure_marshal_VOID__STRING 函数,g_signal_new 函数只向其传递 1 个参数。
  • 第 10 个参数是可变参数,其数量由第 8 个参数决定,用于指定 g_signal_new 函数向 marshal 函数传递的参数类型。由于本例使用的 marshal 函数是 g_cclosure_marshal_VOID__STRING 函数,并且 g_signal_new 函数只向其传递一个参数,所以传入的参数类型为 G_TYPE_STRING(GObject 库类型管理系统中的字符串类型)。

注意,在上述的 g_signal_new 函数的第 7 个参数的解释中,我提到了闭包。事实上,g_signal_new 函数并没有闭包类型的参数,但是它在内部的确是构建了一个闭包,而且是通过它的第 4 个参数实现的。因为 g_signal_new 函数在其内部调用了 g_signal_type_cclosure_new 函数,后者所做的工作就是从一个给定的类结构体中通过内存偏移地址获得回调函数指针,然后构建闭包返于 g_signal_new 函数。既然 g_signal_new 函数的内部是需要闭包的,那么它的第 7~10 个参数自然都是为那个闭包做准备的。

需要注意,g_cclosure_marshal_VOID__STRING 所约定的回调函数类型为:

1

void (*callback) (gpointer instance, const gchar *arg1, gpointer user_data)

这表明 g_cclosure_marshal_VOID__STRING 需要使用者向其回调函数传入 3 个参数,其中前两个参数是回调函数的必要参数,而第 3 个参数,即 userdata,是为使用者留的“后门”,使用者可以通过这个参数传入自己所需要的任意数据。由于 GObject 闭包约定了回调函数的第 1 个参数必须是对象本身,所以 g_cclosure_marshal_VOID__STRING 函数实际上要求使用者向其传入 2 个参数,但是在本例中 g_signal_new 只向其传递了 1 个类型为 G_TYPE_STRING 类型的参数,这有些蹊跷。

这 是因为 g_signal_new 函数所构建闭包只是让信号所关联的数据类型能够有一次可以自我表现的机会,即可以在信号被触发的时候,能够自动调用该数据类型的某个方法,例如 SignalDemo 类结构体的 default_handler 指针所指向的函数。也就是说,SignalDemo 类自身是没有必要向闭包传递那个“userdata”参数的,只是信号的使用者有这种需求。这就是 g_signal_new 的参数中只表明它向闭包传递了 1 个 G_TYPE_STRING 类型参数的缘故。

上面讲的有些凌乱。现在总结一下:g_signal_new 函数内部所构建的闭包,它在被调用的时候,肯定是被传入了 3 个参数,它们被信号所关联的闭包分成了以下层次:

  • 第 1 个参数是信号的默认闭包(信号注册阶段出现)和信号使用者提供的闭包(信号连接阶段出现)所必需的,但是这个参数是隐式存在的,由 g_signal_new 暗自向闭包传递。
  • 第 2 个参数是显式的,同时也是信号的默认闭包和信号使用者提供的闭包所必须的,这个参数由信号的发射函数(例如 g_signal_emit_by_name)向闭包传递。
  • 第 3 个参数也是显式的,且只被信号使用者提供的闭包所关注,这个参数由信号的连接函数(例如 g_signal_connect)向闭包传递。

若要真正明白上述内容,我们必须去构建 SignalDemo 类的使用者,即 main.c 源文件,内容如下:

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

#include "signal-demo.h"

static void

my_signal_handler (gpointer *instance, gchar *buffer, gpointer userdata)

{

        g_print ("my_signal_handler said: %s\n", buffer);

        g_print ("my_signal_handler said: %s\n", (gchar *)userdata);

}

int

main (void)

{

        g_type_init ();

        gchar *userdata = "This is userdata";

        SignalDemo *sd_obj = g_object_new (SIGNAL_TYPE_DEMO, NULL);

        /* 信号连接 */

        g_signal_connect (sd_obj, "hello",

                          G_CALLBACK (my_signal_handler),

                          userdata);

        /* 发射信号 */

        g_signal_emit_by_name (sd_obj,

                               "hello",

                               "This is the second param",

                               G_TYPE_NONE);

        return 0;

}

编译 signal-demo.c 与 main.c:

1

$ gcc signal-demo.c main.c -o test $(pkg-config --cflags --libs gobject-2.0)

程序运行结果如下:

1

2

3

4

$ ./test

Default handler said: This is the second param

my_signal_handler said: This is the second param

my_signal_handler said: This is userdata

结合程序的运行结果,再回顾一下第 1 个实例中的那些乱七八糟的内容,现在应该清晰了许多。

现在,我们再来看一下在第 1 个实例中被我们忽略的 g_signal_new 函数的第  3 个参数,我们将其设为 G_SIGNAL_RUN_FIRST。实际上,这个参数是枚举类型,是信号默认闭包的调用阶段的标识,可以是下面 7 种形式中 1 种,也可以是多种组合。

1

2

3

4

5

6

7

8

9

10

typedef enum

{

  G_SIGNAL_RUN_FIRST = 1 << 0,

  G_SIGNAL_RUN_LAST = 1 << 1,

  G_SIGNAL_RUN_CLEANUP = 1 << 2,

  G_SIGNAL_NO_RECURSE = 1 << 3,

  G_SIGNAL_DETAILED = 1 << 4,

  G_SIGNAL_ACTION = 1 << 5,

  G_SIGNAL_NO_HOOKS = 1 << 6

} GSignalFlags;

这个参数被设为 G_SIGNAL_RUN_FIRST,表示信号的默认闭包要先于信号使用者的闭包被调用,这个观察一下上面的 test 程序的输出结果便可知悉。如果我们将这个参数设为 G_SIGNAL_RUN_LAST,则表示信号的默认闭包要迟于信号使用者的闭包而被调用。对于这个参数的理解暂且到此为止,后面在讲述信号连接的时候 还会再次谈到它。

小结

现在,对信号注册的主要过程已有所了解,但是依 g_signal_new 函数的第 5 个与第 6 个参数,对于它们,我现在还不知道如何为其构建实例,以后再说吧。若你读到此处并且知道它们的用法,还望不吝赐教。

转载时,希望不要链接文中图片,另外请保留本文原始出处:http://garfileo.is-programmer.com

猜你喜欢

转载自blog.csdn.net/knowledgebao/article/details/82492461