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_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_notifier
和g_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_newv
、 g_signal_new_valist
和g_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_hook
和g_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_HOOK和RUN_CLEANUP)的闭包被调用后都会被执行,如果accumulator函数返回不为TRUE,都将使发射直接进入RUN_CLEANUP阶段。
转载于:https://www.cnblogs.com/silvermagic/p/9087883.html
Glib之GObject简介(翻译)相关推荐
- 【GLib】GLib学习笔记(一):GLib、GObject、GType
1.GLib GLib是 Gtk+ 库和 Gnome 的基础.glib 可以在多个平台下使用,比如 Linux.Unix.Windows 等.GLib为许多标准的.常用的 C 语言结构提供了相应的替代 ...
- PhoneME简介(翻译)
PhoneME简介(翻译) 作者:陈跃峰 出自: http://blog.csdn.net/mailbomb phoneME Feature software是一个优化了的Java ME架构.它的核心 ...
- ARM SIMD NEON 简介 (翻译自 Introducing NEON Development Article)
目录 NEON简介 SIMD是什么? ARM SIMD 指令集 NEON是什么? NEON架构概览 支持的数据类型 NEON寄存器 NEON指令 NEON开发 汇编器 Intrinsics 自动向量化 ...
- PyTorch C++ API libtorch 简介
PyTorch C++ API libtorch 简介 翻译自 PyTorch 官方文档:https://pytorch.org/cppdocs/index.html#acknowledgements ...
- NS3_Tutorial 中文版: 第一章 简介
[声明]允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任. http://yangfei1.blog.51cto.com/1471532/368585 转载 ...
- 1_01_GLib库入门与实践_GLib库简介
GLib库是用C语言实现的一个通用的.可移植的实用程序库,由GNOME的GTK小组开发并维护,最早用在GTK+程序开发上,后来被越来越多的程序所引用.刚开始,GLib库叫GLib-1.0,形成于约19 ...
- 经典神经网络论文超详细解读(八)——ResNeXt学习笔记(翻译+精读+代码复现)
前言 今天我们一起来学习何恺明大神的又一经典之作: ResNeXt(<Aggregated Residual Transformations for Deep Neural Networks&g ...
- Quality-Estimation0 (翻译质量评价-使用 BERT 特征训练 QE 模型)
简介 翻译质量评价(Quality Estimation,QE)是机器翻译领域中的一个子任务,大致可分为 Sentence-level QE,Word-level QE,Phrase-level QE ...
- Quality-Estimation1 (翻译质量评价-复现 WMT2018 阿里论文结果)
简介 翻译质量评价(Quality Estimation,QE)是机器翻译领域中的一个子任务,大致可分为 Sentence-level QE,Word-level QE,Phrase-level QE ...
最新文章
- 实验5,利用三层交换机实现VLAN间路由
- [转载]html5教程
- Vue2.0 $set()的正确使用方式
- bzoj2326 [HNOI2011]数学作业
- wamp增加php,新版PHPWAMP自定义添加PHP版本方法步骤
- 【渗透测试】SQL注入笔记
- cocos2d-x物业现场
- r 重命名 列名_R-reshape2
- 统计学基础知识之统计思维
- keil5怎么放大字体_keil5如何设置字体大小-keil设置字体大小的方法
- PC/104总线简述
- 程序员加薪升职之全路径解析
- Matplotlib 绘制条形图
- 运用滤波反投影的方法对图像进行重建matlab仿真
- JMeter性能测试之使用CSV文件参数化
- 如何使用手机将PDF合并呢?分享一个手机合并文件方法
- python使用requests时报错requests.exceptions.SSLError: HTTPSConnectionPool
- linux系统能看抖音吗,用电脑如何刷抖音?电脑刷抖音方法你知多少
- 贝尔宾团队角色理论:附Belbin Team Roles测试 CW CO SH PL RI ME TW FI
- Antd-Upload组件设置fileList属性时onChnage只执行到“uploading“状态引发的一些问题的解决方案