sec4-信号

1 信号

  信号提供了对象之间的通信手段。当某些事情发生或完成时,就会发出信号。

编写信号的步骤如下所示:

  1. 注册一个信号。信号属于对象,所以注册是在对象的类初始化函数中完成的。
  2. 编写一个信号处理程序。信号处理程序是在信号发出时调用的函数。
  3. 连接信号和处理程序。信号连接到g_connect_signal或其族函数的处理程序。
  4. 发出信号。

第一步和第四步是在信号所属的对象上完成的。第三步通常在对象之外完成。

信号的处理过程是复杂的,要解释所有的特征需要很长的时间。本节的内容仅限于编写简单信号的最基本内容,不一定准确。如果您需要准确的信息,请参阅GObject API参考。描述信号有四个部分。

2 信号注册

  本节中的一个例子是在发生除零时发出的信号。首先,我们需要确定信号的名称。信号名称由英文字母、数字、“-”和“_”组成。名称的第一个字符必须是字母。因此,字符串“div-by- zero”适合作为信号名称。

有四个函数来注册一个信号。我们将使用g_signal_new表示“div-by- zero”信号。

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,
              ...);

每个参数都需要解释很多。目前,我只向您展示从tdouble.c提取的g_signal_new函数调用。

t_double_signal =
g_signal_new ("div-by-zero",
              G_TYPE_FROM_CLASS (class),
              G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
              0 /* class offset.Subclass cannot override the class handler (default handler). */,
              NULL /* accumulator */,
              NULL /* accumulator data */,
              NULL /* C marshaller. g_cclosure_marshal_generic() will be used */,
              G_TYPE_NONE /* return_type */,
              0     /* n_params */
              );
  • t_double_signal是一个静态guint变量。guint类型与unsigned int相同。t_double_signal是信号id。
  • 第二个参数是信号所属对象的类型(GType)。G_TYPE_FROM_CLASS (class)返回与类对应的类型(class是指向对象的类的指针)。
  • 第三个参数是一个信号标志。解释该标志需要很多页。现在我想把它们去掉。上面的参数可以在很多情况下使用。该定义在GObject API引用——SignalFlags中描述。
  • 返回类型是G_TYPE_NONE,这意味着信号处理程序没有返回值。
  • n_params是一组参数。这个信号没有参数,所以它是零。

这个函数位于类初始化函数(t_double_class_init)中。

可以使用其他函数,如g_signal_newv。详情参见GObject API参考。

3 信号处理程序(Signal handler)

信号处理程序是在信号发出时调用的函数。处理程序有两个参数。

  • 那个实例发出的信号
  • 指向用户数据的指针,在信号连接中给出。

“div by zero”信号不需要用户数据。

void div_by_zero_cb (TDouble *d, gpointer user_data) {... ... ...}

或者,你可以省略第二个参数。

void div_by_zero_cb (TDouble *d) {... ... ...}

如果信号有参数,则参数一定是实例和用户数据。例如,GtkDialog的“response”信号的处理程序是:

void user_function (GtkDialog *dialog, int response_id, gpointer user_data);

resoponse_id是信号的参数。当用户单击“OK”或“Cancel”按钮时,就会触发“response”信号。response_id的参数是一个值,表示单击了哪个按钮。

“div-by-zero”信号的处理程序只显示一条错误消息。

static void
div_by_zero_cb (TDouble *d, gpointer user_data) {
  g_print ("\nError: division by zero.\n\n");
}

4 信号连接

信号和处理程序通过函数g_signal_connect连接。

g_signal_connect (d1, " div_by_zero "G_CALLBACK (div_by_zero_cb)NULL);
  • d1是信号所属的实例。
  • 第二个参数是信号名称。
  • 第三个参数是信号处理程序。它必须由G_CALLBACK强制转换。
  • 最后一个参数是用户数据。信号不需要用户数据,因此被赋值为NULL。

5 信号发射

信号被发射。下面是tdouble.c的一部分。

TDouble *
t_double_div (TDouble *self, TDouble *other) {
... ... ...
  if ((! t_double_get_value (other, &value)))
    return NULL;
  else if (value == 0) {
    g_signal_emit (self, t_double_signal, 0);
    return NULL;
  }
  return t_double_new (self->value / value);
}

如果除数为零,则发出信号。G_signal_emit有三个参数。

  • 第一个参数是发出信号的实例。
  • 第二个参数是信号id。信号id是函数g_signal_new返回的值。
  • 第三个参数是详细信息。"div-by- 0 "信号没有细节,所以参数为0。本节不会详细解释,但通常你可以把0作为第三个参数。如果你想了解细节,请参考GObject API Reference—Signal Detail。

如果一个信号有参数,它们是第四个和后面的参数。

6 示例

示例程序在src/tdouble3中。

/* filename: tdouble.c */
  1 #include "tdouble.h"
  2 
  3 static guint t_double_signal;
  4 
  5 struct _TDouble {
  6   GObject parent;
  7   double value;
  8 };
  9 
 10 G_DEFINE_TYPE (TDouble, t_double, G_TYPE_OBJECT)
 11 
 12 static void
 13 t_double_class_init (TDoubleClass *class) {
 14   t_double_signal = g_signal_new ("div-by-zero",
 15                                  G_TYPE_FROM_CLASS (class),
 16                                  G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
 17                                  0 /* class offset.Subclass cannot override the class handler (default handler). */,
 18                                  NULL /* accumulator */,
 19                                  NULL /* accumulator data */,
 20                                  NULL /* C marshaller. g_cclosure_marshal_generic() will be used */,
 21                                  G_TYPE_NONE /* return_type */,
 22                                  0     /* n_params */
 23                                  );
 24 }
 25 
 26 static void
 27 t_double_init (TDouble *d) {
 28 }
 29 
 30 /* getter and setter */
 31 gboolean
 32 t_double_get_value (TDouble *d, double *value) {
 33   g_return_val_if_fail (T_IS_DOUBLE (d), FALSE);
 34 
 35   *value = d->value;
 36   return TRUE;
 37 }
 38 
 39 void
 40 t_double_set_value (TDouble *d, double value) {
 41   g_return_if_fail (T_IS_DOUBLE (d));
 42 
 43   d->value = value;
 44 }
 45 
 46 /* arithmetic operator */
 47 /* These operators create a new instance and return a pointer to it. */
 48 #define t_double_binary_op(op) \
 49   if (! t_double_get_value (other, &value)) \
 50     return NULL; \
 51   return t_double_new (self->value op value);
 52 
 53 TDouble *
 54 t_double_add (TDouble *self, TDouble *other) {
 55   g_return_val_if_fail (T_IS_DOUBLE (self), NULL);
 56   g_return_val_if_fail (T_IS_DOUBLE (other), NULL);
 57   double value;
 58 
 59   t_double_binary_op (+)
 60 }
 61 
 62 TDouble *
 63 t_double_sub (TDouble *self, TDouble *other) {
 64   g_return_val_if_fail (T_IS_DOUBLE (self), NULL);
 65   g_return_val_if_fail (T_IS_DOUBLE (other), NULL);
 66   double value;
 67 
 68   t_double_binary_op (-)
 69 }
 70 
 71 TDouble *
 72 t_double_mul (TDouble *self, TDouble *other) {
 73   g_return_val_if_fail (T_IS_DOUBLE (self), NULL);
 74   g_return_val_if_fail (T_IS_DOUBLE (other), NULL);
 75   double value;
 76 
 77   t_double_binary_op (*)
 78 }
 79 
 80 TDouble *
 81 t_double_div (TDouble *self, TDouble *other) {
 82   g_return_val_if_fail (T_IS_DOUBLE (self), NULL);
 83   g_return_val_if_fail (T_IS_DOUBLE (other), NULL);
 84   double value;
 85 
 86   if ((! t_double_get_value (other, &value)))
 87     return NULL;
 88   else if (value == 0) {
 89     g_signal_emit (self, t_double_signal, 0);
 90     return NULL;
 91   }
 92   return t_double_new (self->value / value);
 93 }
 94 TDouble *
 95 t_double_uminus (TDouble *self) {
 96   g_return_val_if_fail (T_IS_DOUBLE (self), NULL);
 97 
 98   return t_double_new (-self->value);
 99 }
100 
101 TDouble *
102 t_double_new (double value) {
103   TDouble *d;
104 
105   d = g_object_new (T_TYPE_DOUBLE, NULL);
106   d->value = value;
107   return d;
108 }
109 
/* filename:main.c */
 1 #include <glib-object.h>
 2 #include "tdouble.h"
 3 
 4 static void
 5 div_by_zero_cb (TDouble *d, gpointer user_data) {
 6   g_printerr ("\nError: division by zero.\n\n");
 7 }
 8 
 9 static void
10 t_print (char *op, TDouble *d1, TDouble *d2, TDouble *d3) {
11   double v1, v2, v3;
12 
13   if (! t_double_get_value (d1, &v1))
14     return;
15   if (! t_double_get_value (d2, &v2))
16     return;
17   if (! t_double_get_value (d3, &v3))
18     return;
19 
20   g_print ("%lf %s %lf = %lf\n", v1, op, v2, v3);
21 }
22 
23 int
24 main (int argc, char **argv) {
25   TDouble *d1, *d2, *d3;
26   double v1, v3;
27 
28   d1 = t_double_new (10.0);
29   d2 = t_double_new (20.0);
30   if ((d3 = t_double_add (d1, d2)) != NULL) {
31     t_print ("+", d1, d2, d3);
32     g_object_unref (d3);
33   }
34 
35   if ((d3 = t_double_sub (d1, d2)) != NULL) {
36     t_print ("-", d1, d2, d3);
37     g_object_unref (d3);
38   }
39 
40   if ((d3 = t_double_mul (d1, d2)) != NULL) {
41     t_print ("*", d1, d2, d3);
42     g_object_unref (d3);
43   }
44 
45   if ((d3 = t_double_div (d1, d2)) != NULL) {
46     t_print ("/", d1, d2, d3);
47     g_object_unref (d3);
48   }
49 
50   g_signal_connect (d1, "div-by-zero", G_CALLBACK (div_by_zero_cb), NULL);
51   t_double_set_value (d2, 0.0);
52   if ((d3 = t_double_div (d1, d2)) != NULL) {
53     t_print ("/", d1, d2, d3);
54     g_object_unref (d3);
55   }
56 
57   if ((d3 = t_double_uminus (d1)) != NULL && (t_double_get_value (d1, &v1)) && (t_double_get_value (d3, &v3))) {
58     g_print ("-%lf = %lf\n", v1, v3);
59     g_object_unref (d3);
60   }
61 
62   g_object_unref (d1);
63   g_object_unref (d2);
64 
65   return 0;
66 }
67 

$ cd tdouble3; _build/tdouble 2>&1
10.000000 + 20.000000 = 30.000000
10.000000 - 20.000000 = -10.000000
10.000000 * 20.000000 = 200.000000
10.000000 / 20.000000 = 0.500000

Error: division by zero.

-10.000000 = -10.000000

7 默认信号处理程序

您可能认为在main.c中设置错误消息很奇怪。事实上,错误发生在tdouble.c中,因此消息应该由tdouble.c本身管理。GObject系统有一个默认的信号处理程序,在对象本身中设置。默认信号处理程序也称为“默认处理程序”或“对象方法处理程序”。

您可以使用g_signal_new_class_handler设置默认处理程序。

guint
g_signal_new_class_handler (const gchar *signal_name,
                            GType itype,
                            GSignalFlags signal_flags,
                            GCallback class_handler, /*default signal handler */
                            GSignalAccumulator accumulator,
                            gpointer accu_data,
                            GSignalCMarshaller c_marshaller,
                            GType return_type,
                            guint n_params,
                            ...);

与g_signal_new不同的是第四个参数。G_signal_new用类结构中函数指针的偏移量设置一个默认处理程序。如果一个对象是可派生的,它有自己的类区域,所以你可以用g_signal_new设置一个默认的处理程序。但是final类型对象没有自己的类区域,所以不可能用g_signal_new设置默认处理程序。这就是我们使用g_signal_new_class_handler的原因。

tdouble.c被修改。添加div_by_zero_default_cb函数,g_signal_new_class_handler替换g_signal_new。默认的信号处理程序没有user_data参数。在用户提供的信号处理程序连接到信号时,用g_signal_connect族函数设置一个user_data参数。因为默认信号处理程序没有连接到g_signal_connect族函数,所以没有提供用户数据作为参数。

 1 static void
 2 div_by_zero_default_cb (TDouble *d) {
 3   g_printerr ("\nError: division by zero.\n\n");
 4 }
 5 
 6 static void
 7 t_double_class_init (TDoubleClass *class) {
 8   t_double_signal =
 9   g_signal_new_class_handler ("div-by-zero",
10                               G_TYPE_FROM_CLASS (class),
11                               G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
12                               G_CALLBACK (div_by_zero_default_cb),
13                               NULL /* accumulator */,
14                               NULL /* accumulator data */,
15                               NULL /* C marshaller */,
16                               G_TYPE_NONE /* return_type */,
17                               0     /* n_params */
18                               );
19 }

g_signal_connect和div_by_zero_cb从main.c中移除。

$ cd tdouble4; _build/tdouble 2>&1
10.000000 + 20.000000 = 30.000000
10.000000 - 20.000000 = -10.000000
10.000000 * 20.000000 = 200.000000
10.000000 / 20.000000 = 0.500000
Error: division by zero.
-10.000000 = -10.000000

源文件位于目录src/tdouble4中。

如果想将处理程序(用户提供的处理程序)连接到信号,仍然可以使用g_signal_connect。在main.c中添加以下代码。

static void
div_by_zero_cb (TDouble *d, gpointer user_data) {
  g_print ("\nError happens in main.c.\n");
}

int
main (int argc, char **argv) {
... ... ...
  g_signal_connect (d1, "div-by-zero", G_CALLBACK (div_by_zero_cb), NULL);
... ... ...
}

然后,在发出信号时,用户提供的处理程序和默认处理程序都会被调用。编译并执行它,然后在显示器上显示以下内容。

10.000000 + 20.000000 = 30.000000
10.000000 - 20.000000 = -10.000000
10.000000 * 20.000000 = 200.000000
10.000000 / 20.000000 = 0.500000
Error happens in main.c.
Error: division by zero.
-10.000000 = -10.000000

这告诉我们,首先调用用户提供的处理程序,然后调用默认的处理程序。如果希望在默认处理程序之后调用处理程序,则可以使用g_signal_connect_after。再次将下面的行添加到main.c中。

static void
div_by_zero_after_cb (TDouble *d, gpointer user_data) {
  g_print ("\nError has happened in main.c and an error message has been displayed.\n");
}

int
main (int argc, char **argv) {
... ... ...
  g_signal_connect_after (d1, "div-by-zero", G_CALLBACK (div_by_zero_after_cb), NULL);
... ... ...
}

编译并执行它,然后:

10.000000 + 20.000000 = 30.000000
10.000000 - 20.000000 = -10.000000
10.000000 * 20.000000 = 200.000000
10.000000 / 20.000000 = 0.500000
Error happens in main.c.
Error: division by zero.
Error has happened in main.c and an error message has been displayed.
-10.000000 = -10.000000

源文件在src/tdouble5中。

8 信号flag

调用处理程序的顺序在GObject API参考—信号发射中描述。
顺序取决于在g_signal_new或g_signal_new_class_handler中设置的信号标志。有三个标志与处理程序调用的顺序有关。

  • G_SIGNAL_RUN_FIRST:默认处理程序在任何用户提供的处理程序之前被调用。
  • G_SIGNAL_RUN_LAST:默认处理程序在普通用户提供的处理程序之后调用(不与g_signal_connect_after连接)
  • G_SIGNAL_RUN_CLEANUP:默认处理程序在用户提供的任何处理程序之后调用。

G_SIGNAL_RUN_LAST在许多情况下是最合适的。

其他信号标志在GObject API Rreference中描述。

翻译来自:GObject tutorial

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值