恶心的GObject[Part I][v0.1]
Posted on Sat, 18 Sep 2010 05:43:43 -1100虽然GObject最初是为了简化C语言的OO开发而设立的,但毕竟C不是天生的OO语言,而其中有太多抽象的东西,反而让其难以入门.....
虽然以前花了很长时间去研究这东西,但限于当时的水平,一些东西并没有弄透彻,甚至有不少错误....
因为前段自己尝试用C语言来模拟OO,积累了不少经验,最近偶然又回顾了下GObject,一些似懂非懂的概念明朗了许多.....
Part I. GObject中最恶心的概念
下面罗列几个概念,都是难以弄明白的....
GCClosure,GClosure,GClosureMarshal,
GClosureNotifyData,GClosureNotify,SignalAccumulator,GSignalAccumulator...
晕了吧,估计不少学了一段Gtk的人对上面的概念还是一知半解...
在具体展开前,我们需要知道GObject是怎么描述类,以及如何进行类型的判定和转换的....
我们知道C语言进行OO编程的时候要大量用到结构体和函数指针,GObject也不例外,
而GObject中每一个类也是一到多个结构体[多个结构体是干嘛的后面会解释],GObject本身用多个int型变量来表示各个类型,类型的检测就是一个int型变量的检查,而类型的转换也是根据这个int型的变量[需要判断是否可以转换],进行结构体或指针的强制转换.
而GCClosure,GClosure就是两个结构体!
前面的GCClosure多了个C,自然是C语言用的,不难想到GCClosure是GClosure一个封装,提供针对C语言的一种绑定,自然,GObject可以绑定到多种语言,比如Python,Ruby等等,目前绑定的比较好的是C[原生]/CPP/VALA/PYTHON
仔细看下
struct _GCClosure { GClosure closure; gpointer callback; };
注意,_GCClosure会用typedef在定义成GCClosure,这一点后面都类似,以后不再特殊说明,
通过GCClosure定义,不难发现其封装了一个GClosure的同时,还有一个callback,猜到了吧!GCClosure的主要功能就是回调!
估计不够淡定的朋友会觉得这也太扯淡了,回调的本质就是一个函数指针,包装这么多层干嘛?GObject这样设计自然有它的道理,
我们知道不同语言有不同的类型,比如C语言里就没有原生的String,
而即便是C语言我们仅在C语言,我们在定义回调函数时也可能要用到不同的形式,比如有的返回int,有的返回void,我们怎么区别这些呢?
不急,待我一步一步分析,
GOBject中GClosureMarshal是一个函数指针,但是要注意它是用来定义回调函数类型的而不是直接调用的!
typedef void (*GClosureMarshal) (GClosure *closure, GValue *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data);
看到它的参数没有,指定了回调返回值类型,回调函数参数的个数等等
而每一个GClosure都要有一个绑定的GClosureMarshal,
具体来看看
struct _GClosure { /*< private >*/ volatile guint ref_count : 15; volatile guint meta_marshal : 1; volatile guint n_guards : 1; volatile guint n_fnotifiers : 2; /* finalization notifiers */ volatile guint n_inotifiers : 8; /* invalidation notifiers */ volatile guint in_inotify : 1; volatile guint floating : 1; /*< protected >*/ volatile guint derivative_flag : 1; /*< public >*/ volatile guint in_marshal : 1; volatile guint is_invalid : 1; /*< private >*/ void (*marshal) (GClosure *closure, GValue /*out*/ *return_value, guint n_param_values, const GValue *param_values, gpointer invocation_hint, gpointer marshal_data); /*< protected >*/ gpointer data; /*< private >*/ GClosureNotifyData *notifiers; };
注意,官方网上的Manual可能描述上过老,容易误导初学者,上面的源自GOjbect源码
罗列上面代码并不是大家一个一个分析的,只是要大家知道前面我说的一个Closure绑定一个Marshaller.....
我前面说过GClosureMarshal是用来定义回调函数类型的,不是用来调用的,GObject中真正的回调是marshal_data[够抽象的,这个是一个void *指针] ,关于这个我不多说什么[因为自己也没时间研究],因为一般不常用,主要用于其它语言间的绑定.
对于C语言,GObject本身已经提供了很多现成的C语言专用的Marshaller,下面给出一个最简单的C专用的Marshaller
/* VOID:VOID (./gmarshal.list:26) */ void g_cclosure_marshal_VOID__VOID (GClosure *closure, GValue *return_value G_GNUC_UNUSED, guint n_param_values, const GValue *param_values, gpointer invocation_hint G_GNUC_UNUSED, gpointer marshal_data) { typedef void (*GMarshalFunc_VOID__VOID) (gpointer data1, gpointer data2); register GMarshalFunc_VOID__VOID callback; register GCClosure *cc = (GCClosure*) closure; register gpointer data1, data2; g_return_if_fail (n_param_values == 1); if (G_CCLOSURE_SWAP_DATA (closure)) { data1 = closure->data; data2 = g_value_peek_pointer (param_values + 0); } else { data1 = g_value_peek_pointer (param_values + 0); data2 = closure->data; } callback = (GMarshalFunc_VOID__VOID) (marshal_data ? marshal_data : cc->callback); callback (data1, data2); }
看到了吧那个callback可以指向marshal_data,而marshal_data也是一个函数指针,一般来说它应指向GCClosure中的callback,只是真正调用时是从GClosure中来调用而已.
而原始的GClosureMarshal本质上也是这样的......
注意VOID_VOID表示回调返回VOID,额外的参数为VOID[就是没有额外的参数]
好了,分析的差不多了,有了上面的基础我们就不难理解signal的链接与回调
对于C语言,
一个SIGNAL在CONNECT时,实际创建了一个C语言的Closure,并绑定了一个C语言的Marshaller,而C语言Marshaller的类型,要在SIGNAL 创建时来定义[注意常见的Gtk中g_signal_connect的常见SIGNAL都是已经定义好回调的类型的,所以你在定义已知信号的回调是要按类型定义],回调函数是在GCClosure中定义的,但本质上是通过GClosure来调用....
简单表示下
GCClosure{ GClosure { ... 其它很多和类型相关的东西; ... GClosureMarshal marshal; //这是个函数指针, //这个的参数中有一个要指向下面定义的callback ... } void *callback; }
而我们在ui使用回调是将SIGNAL发送[EMIT]出去,若在signal队列中有绑定的HANDLER[就是我们的callback],就由其处理...
signal的发送非常复杂,这里不再赘述[如果要研究这个,可以从SignalNode结构入手].....
我们仅需要知道[对于C语言]一旦找到了相应的SIGNAL的ID,就会找到指定GClosure[不是GCClosure],然后通过其找到GClosure并调用其中的marshall函数[传入的参数有何callback相关的],进而调用回调函数.
如果是C语言,可以调用类似g_cclosure_marshal_VOID__VOID的一个定义好的marshal,其内部会调用对应的指向callback的函数[具体参考上面那个C语言VOID_VOID型marshal,调用了传入marshal_data函数指针,也就是我们绑定到GCClosure的callback指针]
好了最扯淡的部分结束了,还有四个[GClosureNotifyData , GClosureNotify , SignalAccumulator , GSignalAccumulator]没说,不过这四个比较简单,理解起来也容易些,下面为了节约时间,一并说了.
GClosureNotifyData 是一个结构体,它包含了一个函数指针GClosureNotify
SignalAccumulator 也是一个结构体,他也包含了一个函数指针GSignalAccumulator
具体代码
struct _GClosureNotifyData { gpointer data; GClosureNotify notify; }; typedef void (*GClosureNotify) (gpointer data, GClosure *closure); typedef struct { GSignalAccumulator func; gpointer data; } SignalAccumulator; typedef gboolean (*GSignalAccumulator) (GSignalInvocationHint *ihint, GValue *return_accu, const GValue *handler_return, gpointer data);
上面的data即回调函数传入的参数
如果我们回调的参数中有定义在堆上的,并且调用完要释放,那么应该通过绑定一个GClosureNotify型的指针,而GSignalAccumulator指针则是用来控制某个信号响应后是否继续向下传递用的.
注意: GClosure中有指向GClosureNotifyData的指针,而SignalNode结构体中有个指向SignalAccumulator结构体的指针.
好了,本篇就到此为止,基本分析了GObject中最难理解的概念,因为写的仓促和个人水平有限希望发现问题的朋友能够给予指正.
我的邮件地址 pingf0@gmail.com