原文地址:http://library.gnome.org/devel/gobject/unstable/signal.html
信号
GObject的信号与标准的UNIX信号无关:它们连接应用程序相关的任一数量监听程序的事件。例如:在GTK+中,每个用户事件(按键或鼠标移动)是从X Server和在一个给出的对象实例的信号发射形式下产生的事件接收到的。
每一个信号同能被发送的类型一起注册到类型系统中:当该类型的用户注册了一个closure到被调用的信号上时,会通知连接(connect)到了给出的类型实例信号。用户也可以通过它们自己发送信号,或从连接到信号的closures内停止信号的发送。
当某个信号被给出的类型实例发射时,所有连接到这个类型实例上这个信号的closures都将被调用。所有连接到这个信号的closures表示的是像下面这样的回调函数:
return_type function_callback (gpointer instance, ... , gpointer user_data);
信号注册
为了注册一个新的信号到一个存在的类型上,我们可以使用g_signal_newv
, g_signal_new_valist
或 g_signal_new
函数中的任何一个。
guint g_signal_newv (const gchar *signal_name,
GType itype,
GSignalFlags signal_flags,
GClosure *class_closure,
GSignalAccumulator accumulator,
gpointer accu_data,
GSignalCMarshaller c_marshaller,
GType return_type,
guint n_params,
GType *param_types);
它的参数定义如下:
signal_name: 它是一个可以唯一标识一个所给信号的字符串
itype: 可以发送信号的实例类型
signal_flags: 在连接到信号的closures中部分定义的调用顺序
class_closure: 这是信号的默认closure:如果在信号发射时为非空(not NULL),它将在信 号发射时被调用。这个时候,被调用的closure会被当成其他的连接到那个信号的closures,并部分依赖于signal_flags.
accumulator: 这是一个函数指针,它在每一个closure被调用后调用。如果它返回为FALSE,信号被停止发射,如果返回为TRUE,信号继续发射。也常常基于所有被调用的closures的返回值来计算这个信号的返回值。
accumulator_data: 这个指针将会在信号发射时传递给每一个accumulator.
c_marshaller: 这是为连接到这个信号的任一一个colsure的默认C的marchaller.
return_type: 这是信号返回值的类型
n_params: 这是信号使用的参数个数
param_types: 这是一个GTypes的数组,它表示信号每个参数的类型。这个数组的长度由n_params指定。
从上面的定义可以看出,一个信号基本上就是一个能连接到这个信号的closures的描述和连接到这个信号的closures被调用的顺序的描述。
信号连接
如果想用closure连接一个信号,可以有三种选择:
l 你可以在信号注册时注册一个class closure: 这是一个全系统操作。例如:这个class_closured 会在所有支持类型的实例每一个信号发送期间被调用。
l 你可以使用g_signal_override_class_closure,它是覆盖一个给出类型的class_closure. 仅仅在信号注册类型的派生类型上调用这个函数是可能的。这个函数仅对语言邦定有用。
l 你可以使用g_signal_connect函数族注册一个closure.这是一个实例相关的操作:这个closure仅仅在指定实例的指定信号发射时才会被调用。
连接一个不同类型的回调函数到指定的信号是可行的:无论哪种实例的信号无论在什么时候发射都会调用发射的钩子函数。发射钩子函数被使用做为在一个应用中得到所有的mouse_clicked发射的例子发出小的鼠标点击声音。发射钩子函数用g_signal_add_emission_hook 连接,用g_signal_remove_emission_hook去除连接。
信号发射
信号的发射是通过使用 g_signal_emit
函数族实现的。
void g_signal_emitv (const GValue *instance_and_params,
guint signal_id,
GQuark detail,
GValue *return_value);
l Gvalues数组instance_and_params包含了输入参数到信号的列表。数组的第一个元素是实例指针,它是调用这个信号的实例。第二个元素包含了到这个信号的参数列表。
l signal_id标识调用的信号。
l detail标识了调用信号的具体细节,detail是一种有魔力的表示或参数,它的传递是在信号发射时,它被连接到这个信号的closures使用来滤出不想要的信号发射。在大多数情况下,你可以安全地设置这个值为0。对这个参数的更详细的描述请参见the section called “The detail argument”
l return_value如果没有累加器被指定,这个参数保存的是在发射期间最后一个被调用的closure的返回值。如果一个累加器(accumulator)在信号创建时被指定,这个累加器被使用来计算return_value,作为在发射期间所有被调用的closures的返回值的函数。如果在发射期间没有closure被调用,return_value仍被初始化为0或null。
在内部,Gvalue数组被传递到正确的发射函数,signal_emit_unlocked_R
(在 gsignal.c
中实现
)。信号发射可以分解为5步:
l RUN_FIRST:如果G_SIGNAL_RUN_FIRST标志在信号注册期间被使用,并且如果这个信号存在一个class_closure, class_closure被调用。然后跳到EMISSION_HOOK状态
l EMISSION_HOOK:如果任一的发射钩子函数被增加到这个信号,他们将按照增加的顺序被调用。累加返回值并跳到HANDLER_RUN_FIRST状态。
l
HANDLER_RUN_FIRST:如果任一的closure用g_signal_connect
函数族被连接,如果他们没有被锁定(用g_signal_handler_block函数族),他们就会被连接的第一个到最后一个的顺序运行。然后跳到RUN_LAST状态。
l RUN_LAST
:如果
G_SIGNAL_RUN_LAST标志在注册期间被设置,且一个class_closure被设置,它会在这里被调用。然后跳到HANDLER_RUN_LAST状态。
l HANDLER_RUN_LAST:如果用g_signal_connect_after
函数族把
任一的closure连接,如果他们没有在HANDLER_RUN_FIRST期间被调用,以及如果没有被锁定,他们就会在这里按照连接顺序运行。然后跳到RUN_CLEANUP状态。
l RUN_CLEANUP:如果G_SIGNAL_RUN_CLEANUP标志在注册期间被设置,并且一个class_closure被设置,它会在这里调用。在这里,整个信号发射全部完成。
如果在发射期间的任一一种状态下(除了RUN_CLEANUP状态),closures中的一个或发射钩子函数用g_signal_stop
停止信号发射,发射就会跳到CLEANUP状态。
如果在发射期间的任一一种状态下,在同样的实例上closures的一个或发射钩子函数发射相同的信号,发射会从RUN_FIRST状态重新开始。
在每一个closure调用后(除了EMISSION_HOOK和CLEANUP),在所有状态累加器函数被调用。它累加closure的返回值到信号的返回值然后返回TRUE或FALSE。如果在任一状态下,没有返回TRUE,发射会跳到CLEANUP状态。
如果没有提供累加器函数,返回值被g_signal_emit
返回的最后一个运行的处理程序返回。
detail参数
所有涉及到信号发射或信号连接的函数都有名为detail的参数。有时,这个参数是被API隐藏了,但是它却总是以一种形式存在。
下面的三个主要的连接函数,仅仅只有一个有清楚的作为Gquark的detail参数:
gulong g_signal_connect_closure_by_id (gpointer instance,
guint signal_id,
GQuark detail,
GClosure *closure,
gboolean after);
另外的两个函数在信号名的标识中隐藏了detail参数。
gulong g_signal_connect_closure (gpointer instance,
const gchar *detailed_signal,
GClosure *closure,
gboolean after);
gulong g_signal_connect_data (gpointer instance,
const gchar *detailed_signal,
GCallback c_handler,
gpointer data,
GClosureNotify destroy_data,
GConnectFlags connect_flags);
它们的detailed_signal参数是一个字符串,它连接信号的名称。然尔,字符串的格式是被构造成像signal_name::detail_namer的格式。 名称为notify::cursor_position的信号连接将真正连接到用cursor_position名字命名为notify的信号。 在内部,如果detail字符串存在,则被转换为GQuark。
下面的四个信号发射函数中,3个有作为GQuark的显式的detail参数:
void g_signal_emitv (const GValue *instance_and_params,
guint signal_id,
GQuark detail,
GValue *return_value);
void g_signal_emit_valist (gpointer instance,
guint signal_id,
GQuark detail,
va_list var_args);
void g_signal_emit (gpointer instance,
guint signal_id,
GQuark detail,
...);
第四个函数在信号名参数中隐藏了此参数。
void g_signal_emit_by_name (gpointer instance,
const gchar *detailed_signal,
...);
参数detailed_signal的格式是与被 g_signal_connect函数使用的格式signal_name::detail_name相同的。
如果detail被发射函数的用户提供,在发射时被使用来与提供detail的clousures匹配。如果这个closures的detail与用户提供的detail不匹配,他们就不会被调用(即使它们被连接到了信号,该信号被发送了)。
这种完全可选的过滤机制主要用于信号的最优化。因为信号常常被许多不同的原因发送:客户可以在closure的调度代码运行前滤出它们感兴趣的事件。例如:GObject的notify信号是非常的有用:在一个GObject对象上,无论什么时候修改了一个属性,GObject会关联修改属性的名字到这个信号发射的detail中,而不是直接发射notify信号。这就允许希望只有一个属性改变时被通知的客户端在接收信号前能滤出大部分的事件。
作为一个简单的规则,用户可以也应该把detail参数设置为0:这将完全禁止可选的过滤。
-------------------------------------------------------
[8] James (again!!) gives a few non-trivial examples of accumulators: “ For instance, you may have an accumulator that ignores NULL returns from closures, and only accumulates the non-NULL ones. Another accumulator may try to return the list of values returned by the closures. ”
[9] A GQuark is an integer which uniquely represents a string. It is possible to transform back and forth between the integer and string representations with the functions
g_quark_from_string
and
g_quark_to_string
.