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_initg_object_do_class_init()}}g_object_new_internal(){// 构造函数被重载的情况g_object_new_with_custom_constructor(){g_object_constructor(){g_type_create_instance(){// 此处就是viewer_file_initinstance_init()}}// 此处就是viewer_file_constructedg_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 handler3,   //the number of parameter types to followG_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阶段。

转载于:https://www.cnblogs.com/silvermagic/p/9087883.html

Glib之GObject简介(翻译)相关推荐

  1. 【GLib】GLib学习笔记(一):GLib、GObject、GType

    1.GLib GLib是 Gtk+ 库和 Gnome 的基础.glib 可以在多个平台下使用,比如 Linux.Unix.Windows 等.GLib为许多标准的.常用的 C 语言结构提供了相应的替代 ...

  2. PhoneME简介(翻译)

    PhoneME简介(翻译) 作者:陈跃峰 出自: http://blog.csdn.net/mailbomb phoneME Feature software是一个优化了的Java ME架构.它的核心 ...

  3. ARM SIMD NEON 简介 (翻译自 Introducing NEON Development Article)

    目录 NEON简介 SIMD是什么? ARM SIMD 指令集 NEON是什么? NEON架构概览 支持的数据类型 NEON寄存器 NEON指令 NEON开发 汇编器 Intrinsics 自动向量化 ...

  4. PyTorch C++ API libtorch 简介

    PyTorch C++ API libtorch 简介 翻译自 PyTorch 官方文档:https://pytorch.org/cppdocs/index.html#acknowledgements ...

  5. NS3_Tutorial 中文版: 第一章 简介

    [声明]允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任. http://yangfei1.blog.51cto.com/1471532/368585 转载 ...

  6. 1_01_GLib库入门与实践_GLib库简介

    GLib库是用C语言实现的一个通用的.可移植的实用程序库,由GNOME的GTK小组开发并维护,最早用在GTK+程序开发上,后来被越来越多的程序所引用.刚开始,GLib库叫GLib-1.0,形成于约19 ...

  7. 经典神经网络论文超详细解读(八)——ResNeXt学习笔记(翻译+精读+代码复现)

    前言 今天我们一起来学习何恺明大神的又一经典之作: ResNeXt(<Aggregated Residual Transformations for Deep Neural Networks&g ...

  8. Quality-Estimation0 (翻译质量评价-使用 BERT 特征训练 QE 模型)

    简介 翻译质量评价(Quality Estimation,QE)是机器翻译领域中的一个子任务,大致可分为 Sentence-level QE,Word-level QE,Phrase-level QE ...

  9. Quality-Estimation1 (翻译质量评价-复现 WMT2018 阿里论文结果)

    简介 翻译质量评价(Quality Estimation,QE)是机器翻译领域中的一个子任务,大致可分为 Sentence-level QE,Word-level QE,Phrase-level QE ...

最新文章

  1. 实验5,利用三层交换机实现VLAN间路由
  2. [转载]html5教程
  3. Vue2.0 $set()的正确使用方式
  4. bzoj2326 [HNOI2011]数学作业
  5. wamp增加php,新版PHPWAMP自定义添加PHP版本方法步骤
  6. 【渗透测试】SQL注入笔记
  7. cocos2d-x物业现场
  8. r 重命名 列名_R-reshape2
  9. 统计学基础知识之统计思维
  10. keil5怎么放大字体_keil5如何设置字体大小-keil设置字体大小的方法
  11. PC/104总线简述
  12. 程序员加薪升职之全路径解析
  13. Matplotlib 绘制条形图
  14. 运用滤波反投影的方法对图像进行重建matlab仿真
  15. JMeter性能测试之使用CSV文件参数化
  16. 如何使用手机将PDF合并呢?分享一个手机合并文件方法
  17. python使用requests时报错requests.exceptions.SSLError: HTTPSConnectionPool
  18. linux系统能看抖音吗,用电脑如何刷抖音?电脑刷抖音方法你知多少
  19. 贝尔宾团队角色理论:附Belbin Team Roles测试 CW CO SH PL RI ME TW FI
  20. Antd-Upload组件设置fileList属性时onChnage只执行到“uploading“状态引发的一些问题的解决方案

热门文章

  1. 连续两次入围Gartner魔力象限的Quick BI到底有何魔力?
  2. ROM、RAM、DRAM、SRAM、FLASH区别
  3. 成都计算机专业考研难度排名,成都计算机考研难度排行榜
  4. 统计学 假设检验 P值
  5. instagram架构_Facebook如何收购Instagram内幕故事
  6. Log4j-自动发送日志邮件
  7. MySQL数据库基础-----多表查询
  8. 计算机怎么进入用户模式,Win7系统怎么进入电脑安全模式?
  9. MySQL数据库实操教程(20)——视图
  10. 数据分析与挖掘(一)误差与精度