1、描述
信号的基本概念是信号发射。信号引入了信号类型并通过字符串进行识别。为父类型引入的信号也可以在派生类型中使用,因此基本上它们是继承的每个类型的工具。
信号发射主要涉及以精确定义的方式调用特定的一组回调。此类回调主要分为两类:对象的回调和用户的回调。(尽管信号可以处理任何类型的可实例化类型,但在下文中,我将这些类型称为“对象类型”,仅仅是因为这是大多数用户会遇到信号的上下文。)每个对象的回调最常见称为“对象方法处理程序”或“默认(信号)处理程序”,而用户提供的回调通常仅称为“信号处理程序”。
对象方法处理程序是在信号创建时提供的(这最经常发生在对象类创建的末尾),而用户提供的处理程序经常在某些对象实例上与特定信号连接或断开连接。
信号发射包括五个阶段,除非过早停止:
1. G_SIGNAL_RUN_FIRST信号的对象方法处理程序的调用
2. 调用普通的用户提供的信号处理程序(after 未设置标志的地方)
3. G_SIGNAL_RUN_LAST信号的对象方法处理程序的调用
4. 调用用户提供的信号处理程序(after 已设置标志)
5. G_SIGNAL_RUN_CLEANUP信号的对象方法处理程序的调用
用户提供的信号处理程序按其连接顺序调用。
所有处理程序都可能过早停止信号发射,并且在信号发射期间可以连接,断开连接,阻塞或解除阻塞任何数量的处理器。
在信号发射的第2阶段和第4阶段,有某些条件可以跳过用户处理程序。
首先,用户处理程序可能被阻止。在回调调用期间,被忽略的处理程序被省略,为了从被阻塞状态返回,一个处理程序必须被取消阻塞的次数与之前被阻塞的次数完全相同。
其次,在发出G_SIGNAL_DETAILED信号时,detail 传入的附加 参数g_signal_emit()必须与当前受调用的信号处理程序的detail参数匹配。信号处理程序的无详细信息参数的指定(连接时省略信号规范的详细信息部分)用作通配符,并匹配传递给发射的任何详细参数。
尽管自detail 变量通常用于传递对象属性名称(如“ notify”一样),但对于详细信息字符串,没有强制要求使用任何特定格式,只是该格式必须为非空。
信号处理程序的内存管理
如果要将处理程序连接到信号并使用GObject实例作为信号处理程序用户数据,则应记住将的调用g_signal_connect()与g_signal_handler_disconnect()或的 调用配对 g_signal_handlers_disconnect_by_func()。当确定发出信号的对象时,信号处理程序会自动断开连接,而销毁信号处理程序的用户数据时,信号处理程序不会自动断开连接。如果此用户数据是GObject实例,则在完成后从信号处理程序使用它是一个错误。
有两种用于管理此类用户数据的策略。第一个是在确定用户数据(对象)后断开信号处理程序(使用g_signal_handler_disconnect()或 g_signal_handlers_disconnect_by_func());这必须手动实现。对于非线程程序, g_signal_connect_object()可用于自动实现。但是,当前在线程程序中使用是不安全的。
第二个是对用户数据保持强大的参考,直到由于其他原因导致信号断开之后。可以使用自动实现g_signal_connect_data()。
建议采用第一种方法,因为如果信号处理程序由于某种原因从未断开连接,则第二种方法可能导致有效的用户数据内存泄漏。
2、自定义信号实例
signal-demo.h 内容如下
#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
signal-demo.c 内容如下:
#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;
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 库类型管理系统中的字符串类型)。
必须去构建 SignalDemo 类的使用者,即 main.c 源文件
#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:
$ gcc signal-demo.c main.c -o test $(pkg-config --cflags --libs gobject-2.0)
程序运行结果如下:
我们再来看一下在第 1 个实例中被我们忽略的 g_signal_new 函数的第 3 个参数,我们将其设为 G_SIGNAL_RUN_FIRST。实际上,这个参数是枚举类型,是信号默认闭包的调用阶段的标识,可以是下面 7 种形式中 1 种,也可以是多种组合。这个参数被设为 G_SIGNAL_RUN_FIRST,表示信号的默认闭包要先于信号使用者的闭包被调用,这个观察一下上面的 test 程序的输出结果便可知悉。如果我们将这个参数设为 G_SIGNAL_RUN_LAST,则表示信号的默认闭包要迟于信号使用者的闭包而被调用。对于这个参数的理解暂且到此为止,后面在讲述信号连接的时候 还会再次谈到它。