恶心的GObject[Part I][v0.1]

时间:2021-11-18 20:07:57

恶心的GObject[Part I][v0.1]

转载请注明出处 http://www.pingf.me

虽然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

仔细看下

?
1
2
3
4
5
struct _GCClosure
{
   GClosure  closure;
   gpointer  callback;
};

 

注意,_GCClosure会用typedef在定义成GCClosure,这一点后面都类似,以后不再特殊说明,

通过GCClosure定义,不难发现其封装了一个GClosure的同时,还有一个callback,猜到了吧!GCClosure的主要功能就是回调!

估计不够淡定的朋友会觉得这也太扯淡了,回调的本质就是一个函数指针,包装这么多层干嘛?GObject这样设计自然有它的道理,

我们知道不同语言有不同的类型,比如C语言里就没有原生的String,

而即便是C语言我们仅在C语言,我们在定义回调函数时也可能要用到不同的形式,比如有的返回int,有的返回void,我们怎么区别这些呢?

不急,待我一步一步分析,

GOBject中GClosureMarshal是一个函数指针,但是要注意它是用来定义回调函数类型的而不是直接调用的!

?
1
2
3
4
5
6
typedef void  (*GClosureMarshal)    (GClosure   *closure,
                      GValue         *return_value,
                      guint           n_param_values,
                      const GValue   *param_values,
                      gpointer        invocation_hint,
                      gpointer    marshal_data);

 

看到它的参数没有,指定了回调返回值类型,回调函数参数的个数等等

而每一个GClosure都要有一个绑定的GClosureMarshal,

具体来看看

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* 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来调用....

简单表示下

?
1
2
3
4
5
6
7
8
9
10
11
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

具体代码

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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