Glib之GObject简介(翻译)

GObject

GObject库是Glib库的动态类型系统实现,它实现了:

  • 基于引用计数的内存管理
  • 实例的构造和析构
  • 通用的set/get的属性获取方法
  • 简单易用的信号机制

对象实例化

所述g_object_new的功能家族可用于实例化从GObject的基类型继承的任何的GType。所有这些函数都确保类和实例结构已经被GLib的类型系统正确地初始化,然后在一个或另一个地方调用用于的构造函数类方法:

  • 调用g_type_create_instance分配并清空内存
  • 根据构造参数初始化对象实例

虽然人们可以期望所有的类和实例成员(除了指向父母的字段)被设置为零,但是有些人认为明确地设置它们是一个好习惯。一旦所有施工操作完成并且构造器属性设置完毕,就调用构造的类方法。从GObject继承的对象被允许覆盖这个构造的类方法。以下示例显示了ViewerFile如何覆盖父项目的构建过程:

#define VIEWER_TYPE_FILE viewer_file_get_type ()
G_DECLARE_FINAL_TYPE(ViewerFile, viewer_file, VIEWER, FILE, GObject)

struct _ViewerFile
{
  GObject parent_instance ;

  / *实例成员* /
};

/ *将创建viewer_file_get_type并设置viewer_file_parent_class * /
G_DEFINE_TYPE(ViewerFile, viewer_file, G_TYPE_OBJECT)

static void
viewer_file_constructed (GObject * obj)
{
  / *根据构造函数属性更新对象状态* /

  / *始终链接到父构造函数以完成对象
   初始化。* /
  G_OBJECT_CLASS(viewer_file_parent_class)->constructed(obj);
}

static void
viewer_file_class_init (ViewerFileClass * klass)
{
  GObjectClass * object_class =  G_OBJECT_CLASS(klass);

  object_class->constructed = viewer_file_constructed;
}

static void
viewer_file_init (ViewerFile * self)
{
  / *初始化对象* /
}

如果用户用下面的方式实例化一个对象ViewerFile:

ViewerFile *file = g_object_new (VIEWER_TYPE_FILE, NULL);

如果这是这样一个对象的第一个实例化,那么 viewer_file_class_init函数将在任何viewer_file_base_class_init函数之后被调用。这将确保这个新对象的类结构被正确初始化,在这里viewer_file_class_init预计会覆盖GObject的类方法并设置类自己的方法。在上面的例子中,构造函数方法是唯一被覆盖的方法:它被设置为 viewer_file_constructor

一旦g_object_new获得了一个初始化类结构的引用,它就调用它的构造函数方法来创建新对象的一个实例,如果构造函数已被覆盖viewer_file_class_init,重写的构造函数必须链接到父项的构造函数,为了找到父类和链父类的构造函数,我们可以使用宏viewer_file_parent_class为我们设置的指针G_DEFINE_TYPE

最后由链中最后一个构造函数调用g_object_constructor。这个函数通过g_type_create_instance分配对象的实例缓冲区,这时候如果注册了instance_init函数,将会被调用。在instance_init返回后,对象完成初始化,并允许用户调用其方法。当 g_type_create_instance返回时,g_object_constructor将设置构造属性(执行g_object_new时传入的参数),并返回到用户的构造函数。

上面描述的过程可能看起来有点复杂,但是可以通过下面的表格容易地总结,其中列出了调用的函数g_object_new及其调用顺序:

// 下面只是伪代码,很多函数可能是都是调用链,例如g_object_constructor()等,这边都只使用最后调用的g_object_XX函数来代替
g_object_new
{
    // 注册类信息
    g_type_class_ref()
    {
        type_class_init_Wm()
        {
        g_object_base_class_init()
        // 此处就是viewer_file_class_init
        g_object_do_class_init()
        }
    }

    g_object_new_internal()
    {
        // 构造函数被重载的情况
        g_object_new_with_custom_constructor()
        {
            g_object_constructor()
            {
                g_type_create_instance()
                {
                    // 此处就是viewer_file_init
                    instance_init()
                }
            }
            // 此处就是viewer_file_constructed
            g_object_constructed()
        }
    }
}
调用时间 函数调用 函数的参数 备注
首次调用g_object_new创建目标类型 目标类型的base_class_init函数 从基类开始调用base_class_init,然后递归调用子类,直到到达目标类型。 从未在实践中使用,你不太可能会需要它。
首次调用g_object_new创建目标类型 目标类型的class_init函数 目标类型的类结构 在这里您应该确保初始化或重写类方法(即为每个类的方法分配其函数指针),并创建与对象关联的信号和属性。
首次调用g_object_new创建目标类型 接口的base_init函数 接口的vtable -
首次调用g_object_new创建目标类型 接口的interface_init函数 接口的vtable -
每次调用g_object_new创建目标类型 目标类型的类constructor方法:GObjectClass->constructor 对象的实例 如果您需要以自定义的方式处理构造属性或者实现一个单例类,请重写构造方法,并确保在执行自己的初始化之前链接到对象的父类。如果存在疑问,则不要重写构造方法。
每次调用g_object_new创建目标类型 目标类型的instance_init函数 从基类开始调用instance_init,然后递归调用子类,直到到达目标类型。 提供一个instance_init函数来初始化你的对象,在它的构造属性被设置之前。这是初始化GObject实例的首选方法。这个函数相当于C++的构造函数。
每次调用g_object_new创建目标类型 目标类型的类constructed方法:GObjectClass->constructed 对象的实例 如果在所有构造属性设置完毕后需要执行对象初始化步骤。这是对象初始化过程的最后一步,只有当constructor方法返回一个新的对象实例(而不是现有的单例)时才被调用。

读者应该关心函数调用顺序的一点点转变:从技术上讲,类的构造函数方法是在 GType的instance_init 函数之前g_type_create_instance调用的(因为哪个调用instance_init是由g_object_constructor顶层类的构造方法调用的 ,哪些用户需要连接到),用户提供的构造函数中运行的代码将始终在 GType的instance_init函数之后运行,因为在执行任何有用的操作之前,用户提供的构造函数必须(您已经被警告)链接起来。

内存管理

引用计数

使用线程安全的g_object_ref()/g_object_unref()函数来增加和减少对象引用计数。调用g_object_new后引用计数被初始化成1。当引用计数减为0时,g_object_unref还将调用析构函数释放对象。

调用时间 涉及函数 函数参数 备注
目标类型实例最后一次调用g_object_unref 目标类型的dispose函数 GObject实例 当废弃函数执行完成后,对象将不再拥有任何成员变量对象的引用(对象本身的内存未被释放),虽然还能够被使用(在析构函数被调用前),当然很可能会返回错误码,但是不会抛出内存异常。废弃函数可以被多次调用而不用担心会抛出异常。废弃函数在函数返回前要调用父类的废弃函数实现,保持调用链的完整。
目标类型的finalize函数 GObject实例 析构函数将完成废弃函数的后续动作-释放对象内存。析构函数只能够被调用一次,并且同废弃函数一样,需要在函数返回前调用父类的析构函数实现。
目标类型的最后一个实例最后一次调用g_object_unref 接口的interface_finalize函数 接口的虚表vtable 不要在实践中使用,除非有特殊需要
接口的base_finalize函数 接口的虚表vtable 不要在实践中使用,除非有特殊需要
接口的class_finalize函数 目标类型的类结构 不要在实践中使用,除非有特殊需要
接口的base_finalize函数 从基础类型到目标类型的的继承树上的每个类结构,都调用一次base_finalize 不要在实践中使用,除非有特殊需要

弱引用

弱引用通常用来监视对象的析构,通过g_object_weak_ref添加一个在对象析构时被调用的监控回调函数,这样就可以在不调用g_object_ref的情况下安全的保存一个对象的指针。

void g_object_weak_ref(GObject *object, // 需要建立弱引用的GObject对象
    GWeakNotify notify, // 对象被释放前需要调用的回调函数
    gpointer data); // 传递给回调函数的参数

void (*GWeakNotify)(gpointer data, // 弱连接建立时传入的数据,一般是希望保存对象指针的GObject对象
    GObject *where_the_object_was); // 被析构的弱引用的GObject对象

消息系统

闭包

闭包是在GTK+和GNOME应用中用来表示回调函数的一种通用的抽象方法,闭包结构主要包括三个内容:

  • 回调函数本身的函数指针,如下:
return_type function_callback (… , gpointer user_data);
  • 传递给回调函数的用户数据指针user_data
  • 闭包的析构函数

一个闭包会提供如下简单的服务:

  • 闭包的调用g_closure_invoke:对调用者隐藏回调函数调用细节
  • 通知:闭包会通知监听者某些事件,例如闭包的调用、失效以及终止。通过g_closure_add_finalize_notifier函数注册监听终止通知、g_closure_add_invalidate_notifier函数注册监听失效通知,以及g_closure_add_marshal_guards函数注册监听调用通知。通过g_closure_remove_finalize_notifierg_closure_remove_invalidate_notifier 函数可移除监听。

C 语言闭包

如果想使用C或C++关联回调函数到某个事件上,可使用GCClosures提供的最简单的API函数g_signal_connect

g_cclosure_new/g_cclosure_new_swap将会创建一个调用用户自定义回调函数的闭包,并且使用用户提供的数据作为回调函数入参。闭包终止后使用destroy_data函数进行析构。

Non-C 语言闭包

闭包隐藏了回调函数调用的细节。在C语言中,回调函数的调用就和函数调用很类似:就是为调用函数创建正确的堆栈,然后执行汇编指令。

C闭包方法将表示函数参数的GValues数组转换成C类型的函数参数列表,然后调用用户提供的C函数,获取函数的返回值并将其转换成GValue类型返回给方法调用者。下面就是一个简单的闭包例子:

g_cclosure_marshal_VOID__INT (GClosure     *closure,
                              GValue       *return_value,
                              guint         n_param_values,
                              const GValue *param_values,
                              gpointer      invocation_hint,
                              gpointer      marshal_data)
{
  typedef void (*GMarshalFunc_VOID__INT) (gpointer     data1,
                                          gint         arg_1,
                                          gpointer     data2);
  register GMarshalFunc_VOID__INT callback;
  register GCClosure *cc = (GCClosure*) closure;
  register gpointer data1, data2;

  g_return_if_fail (n_param_values == 2);

  data1 = g_value_peek_pointer (param_values + 0);
  data2 = closure->data;

  callback = (GMarshalFunc_VOID__INT) (marshal_data ? marshal_data : cc->callback);

  callback (data1,
            g_marshal_value_peek_int (param_values + 1),
            data2);
}

信号

GObject的信号和UNIX系统的信号没有任何关系。

信号的注册

我们通常使用g_signal_newvg_signal_new_valistg_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 触发信号的实例类型(g_signal_connect的第一个参数的类型)
signal_flags 定义关联到信号上的闭包的调用顺序
class_closure 信号的默认闭包,如果它不为空,当信号被触发时候它将被调用,调用的时间取决于signal_flags(g_signal_new中使用G_STRUCT_OFFSET宏获取类成员函数作为默认闭包)
accumulator 一个函数指针,每个闭包被调用后都会执行此函数,如果函数返回FALSE,信号发射将停止,否则继续。它通常可以用来统计与信号关联的闭包的调用返回值。
accumulator_data accumulator函数的入参
c_marshaller 关联到此信号的回调方法类型(方法的返回值和参数类型列表)
return_type 信号的返回类型
n_params c_marshaller的参数个数
n_params c_marshaller的参数类型列表
g_signal_new("session-display-removed",                 G_OBJECT_CLASS_TYPE(object_class),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_HOOKS,     G_STRUCT_OFFSET(VirtViewerSessionClass, session_display_removed),
                 NULL,
                 NULL,
g_cclosure_marshal_VOID__OBJECT,
                 G_TYPE_NONE,
                 1,
VIRT_VIEWER_TYPE_DISPLAY);

    signals[SPICE_MAIN_AGENT_GOT_REAL_RESOLUTION] =
        g_signal_new("real-resolution",
                     G_OBJECT_CLASS_TYPE(gobject_class),
                     G_SIGNAL_RUN_LAST ,
                     0,  // Pass 0 to not associate a class method slot with this signal.(如果关联类方法,则设置为G_STRUCT_OFFSET(SpiceSessionClass, channel_new))
                     NULL, NULL,
                     g_cclosure_user_marshal_VOID__INT_INT_INT,
                     G_TYPE_NONE, //return type of handler
                     3,   //the number of parameter types to follow
                     G_TYPE_INT, G_TYPE_INT, G_TYPE_INT  // a list of types, one for each parameter
                     ) ;

//============绑定类方法===============
struct _SpiceSessionClass
{
    GObjectClass parent_class;

    /* signals */
    void (*channel_new)(SpiceSession *session, SpiceChannel *channel);
    void (*channel_destroy)(SpiceSession *session, SpiceChannel *channel);

    /*< private >*/
    /*
     * If adding fields to this struct, remove corresponding
     * amount of padding to avoid changing overall struct size
     */
    gchar _spice_reserved[SPICE_RESERVED_PADDING];
};

关联信号

如果你想关联一个闭包到信号上,你有三种选择:

  • 在信号注册的时候注册一个类闭包,这是一个系统范围内的操作:类闭包在信号每次被触发时都被会调用,与触发信号的实例无关
  • 使用g_signal_override_class_closure重写类闭包,可在信号的继承类型上调用此函数
  • 使用g_signal_connect家族函数,这是一个实例范围的操作:只有当给定的实例触发信号时,才会调用闭包

使用g_signal_add_emission_hookg_signal_remove_emission_hook可以创建全局的触发钩子,且与触发信号的实例无关

如果关联的函数的参数多于定义的信号函数,那么需要在关联的时候传入,例如:

### 信号声明
struct _SpiceSessionClass
{
    GObjectClass parent_class;

    /* signals */
    void (*channel_new)(SpiceSession *session, SpiceChannel *channel);
    void (*channel_destroy)(SpiceSession *session, SpiceChannel *channel);

    /*< private >*/
    /*
     * If adding fields to this struct, remove corresponding
     * amount of padding to avoid changing overall struct size
     */
    gchar _spice_reserved[SPICE_RESERVED_PADDING];
};

### 处理函数声明
static void channel_new(SpiceSession *s, SpiceChannel *channel, gpointer data);

### 关联信号
g_signal_connect(conn->session, "channel-new", G_CALLBACK(channel_new), conn);

信号的触发

使用g_signal_emit家族函数来触发信号

void g_signal_emitv (const GValue *instance_and_params,
                     guint         signal_id,
                     GQuark        detail,
                     GValue       *return_value);
参数名 说明
instance_and_params GValues数组保存的信号的参数列表,数组的首元素是触发信号的对象实例指针
signal_id 被触发的信号标识
detail 描述信号被触发的细节标识
return_value 保存在没有accumulator情况下最后一个闭包调用放返回值
g_signal_emit_by_name(session, "session-display-added", display);
信号触发的五个阶段:
阶段 说明
RUN_FIRST 如果信号注册时使用了G_SIGNAL_RUN_FIRST标志,并且存在类闭包,那么类闭包将被调用
EMISSION_HOOK 如果信号被关联了触发钩子,那么将按照关联顺序依次调用钩子函数
HANDLER_RUN_FIRST 使用g_signal_connect关联的闭包将按照关联时的顺序被依次调用
RUN_LAST 如果信号注册时使用了G_SIGNAL_RUN_LAST标志,并且存在类闭包,那么类闭包将被调用
HANDLER_RUN_LAST 使用g_signal_connect_after关联的闭包如果没有在HANDLER_RUN_FIRST中被调用,那么将按照关联时的顺序被依次调用
RUN_CLEANUP 如果信号注册时使用了G_SIGNAL_RUN_CLEANUP标志,并且存在类闭包,那么类闭包将被调用,信号发射到此为止

在信号发射的任意阶段(除RUN_CLEANUP外),任意闭包调用g_signal_stop_emission方法,都将使发射直接进入RUN_CLEANUP阶段。

在信号发射的任意阶段,如果有闭包或钩子再次出发相同信号,都将使发射回到RUN_FIRST阶段。

accumulator函数在所有阶段(除了EMISSION_HOOKRUN_CLEANUP)的闭包被调用后都会被执行,如果accumulator函数返回不为TRUE,都将使发射直接进入RUN_CLEANUP阶段。

posted @ 2018-05-25 12:11  银魔术师  阅读(13013)  评论(0编辑  收藏  举报