Gobject之signal
GObject 信号(Signal)主要用于特定事件与响应者之间的连接,它与操作系统级中的信号没有什么关系。
一般在class_init时,由g_signal_new生成一个新信号句柄并绑定回调函数,也可以使用g_signal_connect连接对象和处理方式(回调函数),由g_signal_emit发出信号触发。
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,
...)
通过上面的函数模型可以看出,其参数个数是可变的。而这些参数有些是必须的,有些可设为NULL,下面可以结合一个基于clutter的实例来解释一下各个参数的含义(SECTION 1和2是从clutter源代码中提取出来的定义示例):
1, const gchar *signal_name: 该参数是定义信号的名字,它由分隔符以及ASCII码中的字母和数字构成,且第一个字符必须是字母,实例中定义的名字为"myself-signal"。(分隔符可以是"-"或"_"——事实上,系统会先调用g_strdelimit把"_"转化为"-"再存储signal_name。因此,在调用g_singal_emit_by_name时,detailed_signal参数中的分隔符必须是"-");
2, GType itype:该参数是signal所依附的类的在GType类型系统中注册时得到的ID,也就是*_get_type()函数的返回值。而在clutter工程中,class_init时可使用G_OBJECT_CLASS_TYPE(klass)来获得,实例中使用CLUTTER_TYPE_ACTOR获取。(clutter _actor应该才是stage和texture、text的基类);
3, GSignalFlags signal_flags:该参数是信号的属性标记,共有七种,其中有提到"per-object handler",将在下面class_offset中介绍:
· G_SIGNAL_RUN_FIRST:调用回调函数时,"per-object handler"对应的回调函数将第一个调用;
· G_SIGNAL_RUN_LAST:调用回调函数时,"per-object handler"对应的回调函数将在用户用g_signal_connect连接的回调函数之后调用,并在用户用g_signal_connect _after连接的回调函数之前调用;
· G_SIGNAL_RUN_CLEANUP:调用回调函数时,"per-object handler"对应的回调函数将最后一个调用;
· G_SIGNAL_NO_RECURSE:信号发射时,如果信号的上次发射还没有结束,那么本次信号发射将不再进行,而只是使上次的信号发射重新开始。
· G_SIGNAL_DETAILED:信号名字可以使用"signal_name::detailed"的形式。
· G_SIGNAL_ACTION:程序员可以在代码中自由地调用g_signal_emit族的函数来发射信号,而不需要把g_signal_emit族的函数放在一段代码中再来调用。
· G_SIGNAL_NO_HOOKS:信号发射过程中不支持钩子函数。
4, guint class_offset:该参数是itype对应的类的class结构中的一个函数指针相对于class结构的实例的首地址的偏移量。该函数指针所对应的函数常被称为"per-object handler","default (signal) handler"或"object methond handler",并将在信号发出后被调用(如调用g_signal_emit_by_name)。常配合宏G_STRUCT_OFFSET使用(该宏能够返回结构体变量的成员相对于该结构体的变量的首地址的偏移量)。如SECTION1中,G_STRUCT_OFFSET (ClutterActorClass, paint),就是指在clutterActorClass类中,"per-object handler”为 paint的函数的偏移地址。而如果将该参数设为0,则表示该类没有"per-object handler"。实例中由于在类中没有定义相应的"per-object handler”,故设为0;
5, GSignalAccumulator accumulator:该参数是一个函数指针,其对应的函数将在该信号的每个handler执行完以后执行。其函数模型可参见SECTION 3。该函数的返回类型为gboolean。如果其返回值为FASLE,则signal发射过程就会被中止(即不再调用后面的hander),否则会继续下去。事实上,"delete-event"等带有event后缀的signal就是利用了这一点——这些信号的某个回调函数如果返回了TRUE,则以后的回调函数就不会被调用。我的理解是,此函数的功能就是决定信号还要不要继续往下走?(注意,如果该signal有accumulator,则回调函数类型(由c_marshaller反映)必须有返回值,否则该signal不能有accumulator,也即在调用g_signal_new时以NULL作为该形参的实参)
6, gpointer accu_data:该参数将作为用户自定义参数传入accumulator所指向的函数中。
7, GSignalCMarshaller c_marshaller:该参数是一个GSignalCMarshall类型的函数指针,其值反映了回调函数的返回值类型和额外参数类型(所谓“额外参数”,即指除回调函数中instance和user_data以外的参数)。
l 例如,g_closure_marshal_VOID_VOID说明该signal的回调函数为以下的callback类型:typedef void (*callback) (gpointer instance, gpointer user_data);
l g_closure_marshal_VOID_POINTER----------typedef void (*callback) (gpointer instance, gpointer arg1, gpointer user_data);
l g_cclosure_marshal_VOID__CHAR ()---------void (*callback) (gpointer instance, gchar arg1, gpointer user_data);
l g_cclosure_marshal_VOID__INT ()-----------void (*callback) (gpointer instance, gint arg1, gpointer user_data);
Base.h
#ifndef _BASE_H_ #define _BASE_H_ //#include <gtk/gtk.h> #include <glib-object.h> #include <stdio.h> typedef struct _TESTBase TESTBase; typedef struct _TESTBaseClass TESTBaseClass; #define TEST_TYPE_BASE (test_base_get_type()) //瀹炰緥绫诲瀷杞崲 #define TEST_BASE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_TYPE_BASE, TESTBase)) //瀹炰緥绫诲瀷鍒ゅ畾 #define TEST_IS_BASE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_TYPE_BASE)) //绫荤粨鏋勮浆鎹? #define TEST_BASE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_TYPE_BASE, TESTBaseClass)) //绫荤粨鏋勫垽瀹? #define TEST_IS_BASE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TEST_TYPE_BASE)) //鑾峰彇绫荤粨鏋? #define TEST_BASE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_TYPE_BASE, TESTBaseClass)) struct _TESTBase { GObject parent; char szName[32]; }; struct _TESTBaseClass { GObjectClass classparent; int iAction; void (*basehello)(TESTBase *obj,int i); }; //GType test_base_get_type(void); void send_basehello_signal(TESTBase *pBase,int i); #endif
Base.c
#include "Base.h" #include <string.h> typedef enum { HELLO_BASE_SIGNAL = 0, LAST_BASE_SIGNAL }; static int basesignals[LAST_BASE_SIGNAL]; G_DEFINE_TYPE(TESTBase,test_base,G_TYPE_OBJECT); static void test_base_init(TESTBase *pBase) { memcpy(pBase->szName,"baseJob",sizeof(pBase->szName)); printf("test_base_init.\n"); } static void test_base_class_destroy(GObject *object) { TESTBase *pTESTBase = NULL; pTESTBase = TEST_BASE(object); printf("test_base_class_destroy.\n"); } static void test_base_class_finalize(GObject *object) { TESTBase *pTESTBase = NULL; pTESTBase = TEST_BASE(object); printf("test_base_class_finalize.\n"); } static void test_base_hello_signal(TESTBase *pBase,int i) { printf("test_base_hello_signal[%p] [%d].\n",pBase,i); } static void test_base_class_init(TESTBaseClass *pBaseClass) { pBaseClass->iAction = 1; //GTK_OBJECT_CLASS(pBaseClass)->destroy = test_base_class_destroy; G_OBJECT_CLASS(pBaseClass)->finalize = test_base_class_finalize; G_OBJECT_CLASS(pBaseClass)->dispose = test_base_class_destroy; pBaseClass->basehello = test_base_hello_signal; basesignals[HELLO_BASE_SIGNAL] = g_signal_new ("basehello", G_TYPE_FROM_CLASS (pBaseClass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (TESTBaseClass, basehello), NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); printf("test_base_class_init.\n"); } void send_basehello_signal(TESTBase *pBase,int i) { g_signal_emit (pBase, basesignals[HELLO_BASE_SIGNAL], 0,i); }
Child1.h
#include "Base.h" typedef struct _TESTChild1 TESTChild1; typedef struct _TESTChild1Class TESTChild1Class; #define TEST_TYPE_CHILD1 (test_child1_get_type()) //瀹炰緥绫诲瀷杞崲 #define TEST_CHILD1(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), TEST_TYPE_CHILD1, TESTChild1)) //瀹炰緥绫诲瀷鍒ゅ畾 #define TEST_IS_CHILD1(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TEST_TYPE_CHILD1)) //绫荤粨鏋勮浆鎹? #define TEST_CHILD1_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TEST_TYPE_CHILD1, TESTChild1Class)) //绫荤粨鏋勫垽瀹? #define TEST_IS_CHILD1_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TEST_TYPE_CHILD1)) //鑾峰彇绫荤粨鏋? #define TEST_CHILD1_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TEST_TYPE_CHILD1, TESTChild1Class)) struct _TESTChild1 { TESTBase parent; char szChild1Name[32]; }; struct _TESTChild1Class { TESTBaseClass classparent; int iChild1Action; void (*helloCb)(TESTChild1 *pBase,int i); }; //GType test_base_get_type(void); void send_hello_signal(TESTChild1 *pBase,int i); //void test_hello_callback_connect(TESTChild1 *pBase,int i); #endif
Child1.c
#include "Child1.h" G_DEFINE_TYPE(TESTChild1,test_child1,TEST_TYPE_BASE); typedef enum { HELLO_SIGNAL = 0, LAST_SIGNAL }; static int signals[LAST_SIGNAL]; static void test_child1_class_destroy(GObject *object) { TESTBase *pTESTBase = NULL; pTESTBase = TEST_BASE(object); printf("test_child1_class_destroy.\n"); G_OBJECT_CLASS(test_child1_parent_class)->dispose(object); } static void test_child1_class_finalize(GObject *object) { TESTChild1 *pTESTChild1 = NULL; pTESTChild1 = TEST_BASE(object); printf("test_child1_class_finalize.\n"); G_OBJECT_CLASS(test_child1_parent_class)->finalize(object); //GTK_OBJECT_CLASS (test_child1_parent_class)->destroy (object); } static void test_child1_init(TESTChild1 *pBase) { printf("test_child1_init [%s].\n",pBase->parent.szName); } static void test_hello_callback(TESTChild1 *pBase,int i) { printf("TESTChild1[%p] recv hello signal[%d].\n",pBase,i); } static void test_child1_class_init(TESTChild1Class *pBaseClass) { printf("test_child1_class_init [%d].\n",pBaseClass->classparent.iAction); //GTK_OBJECT_CLASS(pBaseClass)->destroy = test_child1_class_destroy; G_OBJECT_CLASS(pBaseClass)->dispose = test_child1_class_destroy; G_OBJECT_CLASS(pBaseClass)->finalize = test_child1_class_finalize; pBaseClass->helloCb = test_hello_callback; signals[HELLO_SIGNAL] = g_signal_new ("hello", G_TYPE_FROM_CLASS (pBaseClass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (TESTChild1Class, helloCb), NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); } void send_hello_signal(TESTChild1 *pBase,int i) { g_signal_emit (pBase, signals[HELLO_SIGNAL], 0,i); }
main.c
#include "Child1.h" static void test_hello_callback_connect(TESTChild1 *pBase,int i,gpointer user_data) { int j = 0; printf("TESTChild1[%p] recv hello signal connect[%d].\n",pBase,i); for(j = 0;j < 2047483648;j++); sleep(3); printf("TESTChild1[%p] recv hello signal connect[%d] end.\n",pBase,i); } static void test_hello_callback_connect1(TESTChild1 *pBase,int i,gpointer user_data) { int j = 0; printf("TESTChild1[%p] recv hello signal connect1[%d].\n",pBase,i); for(j = 0;j < 2047483648;j++); sleep(3); printf("TESTChild1[%p] recv hello signal connect1[%d] end.\n",pBase,i); } int main (void) { g_type_init (); int i; TESTChild1 *P,*P1,*P2; #if 1 P = g_object_new (TEST_TYPE_CHILD1, NULL); P1 = g_object_new (TEST_TYPE_CHILD1, NULL); P2 = g_object_new (TEST_TYPE_CHILD1, NULL); printf("g_object_new P[%p] P1[%p] P2[%p].\n",P,P1,P2); g_signal_connect(G_OBJECT(P), "hello", G_CALLBACK (test_hello_callback_connect1),NULL); g_signal_connect(G_OBJECT(P), "hello", G_CALLBACK (test_hello_callback_connect),NULL); printf("start send signal.\n"); send_hello_signal(P,1); send_hello_signal(P1,2); send_hello_signal(P2,3); send_basehello_signal(P1,4); printf("end send signal.\n"); g_object_unref (P); g_object_unref(P1); g_object_unref(P2); #else P = g_object_new (TEST_TYPE_CHILD1, NULL); g_object_unref (P); printf("new second object.\n"); P = g_object_new (TEST_TYPE_CHILD1, NULL); g_object_unref (P); #endif return 0; }
编译命令:
gcc -Wall Base.c Child1.c main.c -o test $(pkg-config --cflags --libs gobject-2.0)
运行结果:
./test test_base_class_init. test_child1_class_init [1]. test_base_init. test_child1_init [baseJob]. test_base_init. test_child1_init [baseJob]. test_base_init. test_child1_init [baseJob]. g_object_new P[0x23e91e0] P1[0x23e9240] P2[0x23e92a0]. start send signal. TESTChild1[0x23e91e0] recv hello signal connect1[1]. TESTChild1[0x23e91e0] recv hello signal connect1[1] end. TESTChild1[0x23e91e0] recv hello signal connect[1]. TESTChild1[0x23e91e0] recv hello signal connect[1] end. TESTChild1[0x23e91e0] recv hello signal[1]. TESTChild1[0x23e9240] recv hello signal[2]. TESTChild1[0x23e92a0] recv hello signal[3]. test_base_hello_signal[0x23e9240] [4]. end send signal. test_child1_class_destroy. test_base_class_destroy. test_child1_class_finalize. test_base_class_finalize. test_child1_class_destroy. test_base_class_destroy. test_child1_class_finalize. test_base_class_finalize. test_child1_class_destroy. test_base_class_destroy. test_child1_class_finalize. test_base_class_finalize.
总结:
1、信号发送后只有发送函数g_signal_emit指定的实例会有响应调用回调函数。
2、同一个实例上面注册同一个信号的多个回调函数时,响应的速度取决于调用g_signal_connect注册的顺序。g_signal_connect和g_signal_new直接的回调函数调用顺序取决于g_signal_new的第三个参数。
3、基类中的注册的信号,向子类发送信号后仍然会响应。