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

Glib之GObject简介(翻译)的更多相关文章

  1. Flask-Babel 使用简介(翻译文档)

    最近用flask-bable翻译一个项目,在网站上查找到有一个示例文档,地址:http://translations.readthedocs.io/en/latest/flask-babel.html ...

  2. Glib之GObject宏介绍

    G_DEFINE_TYPE定义一个静态类型 /** * G_DEFINE_TYPE(`G_DEFINE_TYPE_WITH_CODE`比`G_DEFINE_TYPE`就是多了一个自定义代码参数_C_) ...

  3. [翻译] ASP.NET Core 简介

    ASP.NET Core 简介 原文地址:Introduction to ASP.NET Core         译文地址:asp.net core 简介           翻译:ganqiyin ...

  4. glib实践篇:父类与子类

    前言: 众所周知,C语言是一门面向过程的语言,但是不代表就得跟面向对象完全绝缘,在C语言库glib中有gobject那么一套面向对象的机制,基于C语言的面向对象设计便是基于该实现机制. 今天所要实践的 ...

  5. 【翻译】ASP.NET Core 入门

    ASP.NET Core 入门 原文地址:Introduction to ASP.NET Core         译文地址:asp.net core 简介           翻译:ganqiyin ...

  6. Android自动化学习笔记之MonkeyRunner:官方介绍和简单实例

    ---------------------------------------------------------------------------------------------------- ...

  7. 开源的DevOps开发工具箱

    DevOps是一组过程.方法与系统的统称,用于促进开发(应用程序/软件工程).技术运营和质量保障(QA)部门之间的沟通.协作与整合.在DevOps的整个流程中,使用一些开源工具可以促进开发与运维之间的 ...

  8. openerp模块收藏 移除下拉选择列表中的“创建并编辑”链接(转载)

    移除下拉选择列表中的“创建并编辑”链接 原文:http://shine-it.net/index.php/topic,5990.0.html 有时希望下拉列表中列出的项是与主表某个字段关联的,用户只能 ...

  9. (转)Google Fonts 的介绍与使用

    转载自“前端笔记”  http://www.cnblogs.com/milly/archive/2013/05/10/google-fonts.html Google Fonts 是什么?(以下翻译为 ...

随机推荐

  1. BZOJ3809:Gty的二逼妹子序列

    浅谈莫队:https://www.cnblogs.com/AKMer/p/10374756.html 题目传送门:https://lydsy.com/JudgeOnline/problem.php?i ...

  2. flask之python3 虚拟环境及使用dotnv来永久保存环境变量

    Python 3 comes bundled with the venv module to create virtual environments Create an environment Cre ...

  3. 机器学习:PCA(基础理解、降维理解)

    PCA(Principal Component Analysis) 一.指导思想 降维是实现数据优化的手段,主成分分析(PCA)是实现降维的手段: 降维是在训练算法模型前对数据集进行处理,会丢失信息. ...

  4. sql语句中GROUP BY 和 HAVING的使用 count()

    在介绍GROUP BY 和 HAVING 子句前,我们必需先讲讲sql语言中一种特殊的函数:聚合函数, 例如SUM, COUNT, MAX, AVG等.这些函数和其它函数的根本区别就是它们一般作用在多 ...

  5. __align(num) 分析

    这几天用2440读写SD卡(FAT32文件系统),定义了个文件信息的数据结构里边数据类型有unsigned char, unsigned int, unsigned long几种,在从SD卡上读取数据 ...

  6. Docker持续化集成和测试

     基于容器的自动构建:Docker在美团的应用 https://linux.cn/article-5465-1.html Docker持续化集成和测试,关于docker-in-docker问题 h ...

  7. Java学习之数据的时间及热度属性

    背景:在JAVA开发的电商网站中都有海量商品信息,绝大部分电商网站都有为了让用户尽快的获取到想要的商品提供流行商品和推荐商品的概念,我的理解是从两个方面反映了商品的时间维度和热度:流行商品是指横向所有 ...

  8. 设置android的versionCode

    在config.xml里面设置 android-versionCode="1" AndroidManifest.xml 将会修改 android:versionCode=" ...

  9. 01-19asp.net基础--网站登录及验证

    第一步: 1)首先使用“CodeSmith”将Examinee类实体化,并生成实体类连接数据库的方法,存在解决方案下的“App_Code”文件夹下. 修改一下连接某个数据库: private SqlC ...

  10. vmstat详细说明

    下面是关于Unix下vmstat命令的详细介绍,收录在这里,以备日后参考 vmstat是用来实时查看内存使用情况,反映的情况比用top直观一些.作为一个CPU监视器,vmstat命令比iostat命令 ...