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

虽然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中最难理解的概念,因为写的仓促和个人水平有限希望发现问题的朋友能够给予指正.

Over!

From: http://pingf.is-programmer.com/posts/21355.html

Leave a Reply

Your email address will not be published. Required fields are marked *