• 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阶段。

  • 相关阅读:
    JAVA编程-------29、求3*3矩阵对角线元素之和
    JAVA编程---------28、对10个数进行排序(冒泡排序)
    JAVA编程-------------27、100以内的素数
    JAVA编程----------26、请输入星期几的第一个字母来判断一下星期几, 第一个字母相同,则判断第二个字母,以此类推
    JAVA编程-----------25、查找5位数的回文数
    JAVA编程---------24、输入一个数,判断位数,并逆序输出
    JAVA编程------------23、递归
    JAVA编程------------22、利用递归求5!
    JAVA编程--------21、求1!+2!+....+20!=
    JAVA编程------------20、计算2/1+3/2+5/3+8/5+...求前20项之和
  • 原文地址:https://www.cnblogs.com/silvermagic/p/9087883.html
Copyright © 2020-2023  润新知