C语言对象函数库-GObject
2024-10-20 19:51:21 阿炯

GObject 实为GLib对象系统,为C语言提供了强大的面向对象编程能力,使得开发者能够以更加高效和灵活的方式进行编程,它提供了一个轻便的对象系统并支持透明的多语言互通。GObject被设计为可以直接使用在C程序中,也可以被封装至其他语言,例如C++,Java,Perl,以及可以生成C代码的Vala(由此大大简化了GObject代码的书写)等等。采用LGPLv2.1协议授权。


GObject库最初是作为GNOME桌面环境的基础库而开发的,仅依赖于GLib和libc。它的设计目标是为 C 语言提供一种类似于其他面向对象编程语言的对象系统,同时保持 C 语言的高效性和灵活性。随着时间的推移,它逐渐发展成为一个独立的、可重用的库,被广泛应用于各种软件开发项目中。它是GNOME的基石并且在GTK+、Pango、辅助功能工具箱,和大多数GNOME的高级库和应用程序中被广泛使用。在GTK+ 2.0之前,GObject代码是GTK+的一部分(现在GObject这个名字已经不在GTK+中了──取代它的基本类型叫做GtkObject。)


由于对象系统适用的范围更广且较有普适性,所以在GTK+v2.0发布时,对象系统被分离了出来,改放到了另一个函数库。GtkObject在Gtk演进的过程里,大部分与GUI不相关的部分都移到了GObject里,造就了新的共享基础类型GObject的诞生。从2002年3月11日(也就是GTK+v2.0发布的日子)开始,GObject就一直以一个分离的函数库的形式存在。其现在被许多非GUI的程序,像命令行应用程序、服务器应用程序等所使用。虽然GObject有属于它自己的文档集,与独立的函数库,但源代码却是在Glib的源码树里,并且与GLib一起发行。因此,GObject使用与GLib一样的版本号码,而且一般Linux Distro的作法也是把GObject包在GLib里(举例来说,Debian把GObject放在libglib2.0包家族里)。

主要特点

面向对象编程支持

类和对象:允许开发者定义类和创建对象。类可以包含数据成员和成员函数,对象则是类的实例。通过这种方式,开发者可以实现封装、继承和多态等面向对象编程的特性。

信号与槽机制:提供了一种类似于 Qt 的信号与槽机制,用于实现对象之间的事件通知和响应。这种机制使得对象之间的通信更加灵活和高效。

属性系统:支持属性的定义和访问。属性可以是对象的数据成员,也可以是通过成员函数计算得到的值。开发者可以通过属性的设置和获取函数来访问和修改对象的属性。

内存管理

引用计数:GObject库使用引用计数来管理对象的生命周期。当一个对象被创建时,它的引用计数为 1。当其他对象引用该对象时,引用计数会增加。当引用该对象的对象被销毁时,引用计数会减少。当引用计数为 0 时,对象会被自动销毁。这种内存管理方式可以避免内存泄漏和悬空指针等问题。

垃圾回收:除了引用计数,GObject库还提供了一种可选的垃圾回收机制。垃圾回收器会定期扫描内存,查找不再被引用的对象,并将其回收。这种机制可以进一步提高内存管理的效率和可靠性。

跨平台性

GObject库是跨平台的,可以在多种操作系统上运行,包括 Linux、Windows、macOS 等。这使得开发者可以在不同的平台上使用相同的代码库,提高了代码的可移植性和可维护性。它还支持多种编译器,包括 GCC、Clang 等。这使得开发者可以根据自己的需求选择合适的编译器,提高了开发效率。


应用场景

GNOME 桌面环境

GObject 库是 GNOME 桌面环境的基础库之一。GNOME 中的许多应用程序和组件都是使用 GObject 库开发的,如 Nautilus 文件管理器、Evolution 邮件客户端等。为 GNOME 提供了强大的面向对象编程能力和内存管理机制,使得开发者能够以更加高效和灵活的方式开发桌面应用程序。

除了 GNOME 桌面环境,GObject 库还被广泛应用于其他软件开发项目中。例如,GTK+图形库就是基于 GObject 库开发的,它为开发者提供了一套丰富的图形用户界面工具集。许多其他的开源项目也使用了 GObject 库,如 WebKit 浏览器引擎、GStreamer 多媒体框架等。这些项目都受益于 GObject 库的强大功能和灵活性。

学习与使用

学习资源

1、官方文档:GObject 库的官方文档是学习和使用该库的重要资源。官方文档详细介绍了 GObject 库的各个方面,包括 API 参考、教程、示例代码等。

2、在线教程:除了官方文档,还有许多在线教程可以帮助开发者学习 GObject 库。这些教程通常以实际的项目为例,介绍如何使用 GObject 库进行开发。

3、开源项目:学习 GObject 库的最好方法之一是阅读和分析开源项目的代码。可以选择一些使用 GObject 库开发的开源项目,如 GNOME 中的应用程序或其他知名的开源项目,深入了解 GObject 库的实际应用。

使用方法

1、安装:在使用 GObject 库之前,需要先安装该库。可以通过包管理器或从源代码编译安装 GObject 库。

2、编程:使用 GObject 库进行编程需要掌握 C 语言的基础知识和面向对象编程的概念。可以使用 GObject 库提供的 API 来定义类、创建对象、设置属性、发送信号等。

3、调试:在开发过程中,可能会遇到各种问题。可以使用调试工具来帮助查找和解决问题。GObject 库提供了一些调试工具和技巧,可以帮助开发者更好地调试使用该库开发的程序。

类型系统

GObject框架的基层主要依靠泛型与动态类型,称作GType。GType系统保留所有对象的执行时期描述以让glue code能方便地与其他语言作链接。类型系统可以处理任何单一继承的类别架构以及非类别的类型,如不透明指针、字符串跟各种长度的整数与浮点数。

类型系统知道如何复制、指派和释放属于任何已注册类型的值。这对像整数这种不是引用计数(reference-counted)的类型来说是很琐碎的事情,但是对于其他是引用计数的复杂对象来说,是必要的。当类型系统复制了一个引用计数的对象,它仅仅只是增加该对象的引用计数,对复制一个复杂、不是引用计数的对象时,则是以配置存储器的方式建立副本。

这项基本的能力被实现在GValue,它是一个泛型容器的类型,里面可以用来保存注册在类型系统里的类型的值。这样的容器在与动态类型语言沟通时,特别地有用。

基础类型

没有任何关系类别的类型被称作非类别的。这些类型相当于根类别,也就是基础类型,可以被其他类型继承。在GLib 2.9.2里,非类别的内置基础类型有:
空类型,对应到C的void (G_TYPE_NONE);
对应到C的有号、无号char、int、long和64位整数long long (G_TYPE_CHAR, G_TYPE_UCHAR, G_TYPE_INT, G_TYPE_UINT, G_TYPE_LONG, G_TYPE_ULONG, G_TYPE_INT64, and G_TYPE_UINT64);
布尔类型(G_TYPE_BOOLEAN);
枚举类型和"旗标"类型,都对应到C的枚举类型,但后者只使用在位段位上(G_TYPE_ENUM and G_TYPE_FLAGS);
单精度与双精度的IEEE浮点数,对应到C的float跟double(G_TYPE_FLOAT and G_TYPE_DOUBLE);
字符串类型,对应到C的char * (G_TYPE_STRING);
不透明指针,对应到C的void *(G_TYPE_POINTER)。

类别内置的基础类型有:
GObject实体的基底类别类型,标准类别继承树的根(G_TYPE_OBJECT)
基底接口类型,跟基底类别类型很相似,但却是标准接口继承树的根(G_TYPE_INTERFACE)
包装了结构的类型,被用来包装简单的值对象或外来对象在引用计数的"盒子"里(G_TYPE_BOXED)
参数规格对象的类型,用在GObject里作为描述对象属性的元数据(G_TYPE_PARAM)。

可以被系统自动实体化的类型被称作可实体化(instantiable)。这些类型的一个重要特色就是实体的第一个字节永远包含一个指针,指向链接到该实体类型的类别结构(虚拟表格的窗体)。为此,任何可被实体化的类型应该是类别。相对地,任何非类别类型(像整数或字符串)必须是不可实体化。另外,大部分类别类型是可实体化的,但某些类型,像接口类型,就不是。

派生类型

从内置GObject基础类型派生下来的,主要有四种类型:

1、枚举类型和"旗标"类型
一般来说,枚举类型或旗标其实都是整数,以相对口语的单字来表示特定的数值,一般都作为对象属性的类型。GLib提供了glib-mkenums工具,来自动化产生的过程,并产生必要的代码。

2、Boxed类型
有些数据结构很简单,并不需要是一个类别。举例来说,我们可能有个类别,里面需要加个background-color属性,他的值应该是某个结构的实体,所以代码看起来像是这样:struct color { int r,g,b; }。要避免继承GObject的话,我们可以建立一个boxed类型来呈现这样的结构,并且提供复制和释放的处理函数给GType。GObject提供了简便的方法,可以让你为GLib数据类型作包装。

3、不透明的指针类型(Opaque pointer types)
有时候,对象既不需要复制也不需要作引用计数或释放。这样的对象在GObject里,可以当作是不透明指针(G_TYPE_POINTER)。通常被用来参考到特定种类的对象。

4、类别与接口类型
GObject应用程序里的大部分类型都是类别,直接或间接地继承自根类别:GObject。是的,GObject里也可以使用类似Java的接口(interface),虽然很少被使用到。很少使用到的原因可能是因为接口是在GLib 2.4(在2004年3月16日发布)才被加进去。GIMP就有使用到GObject的接口。

消息系统

GObject消息系统由两个互补的部分所组成:closures与信号。

1、Closures:GObject closure是callback(回调)的一般化版本。支持现存已经用C/C++或其他语言写好的closure(当提供绑定时)。这允许以例如Python或Java等写好的代码被GObject closure调用。

2、信号:(Signal)是closure被调用的主要机制。对象向类型系统注册信号listener,在给定的信号与给定的closure间指定对应关系。当注册的信号被发出时,对应的closure就会被调用。在GTK+里,所有内定的GUI事件(像鼠标移动和键盘动作)都会为listeners产生GObject信号以进行运作。

类别实现

每个GObject类别必须包含至少两个结构:类别结构与实体结构。

1、类别结构:其相当于C++类的vtable。第一个元素必须是父类的类别结构。里面包含一组函数指针,也就是类别的虚拟方法。放在这里的变量,就像是C++类里的const或类别层级的成员。

2、实体结构
每个对象实体都将会是这个实体结构的副本,同样地,第一个元素,必须是实体结构的父类(这可以确保每个实体都有个指针可以指向类别结构,所有的基类也同样如此),在归入父类之后,可以在结构内放其他的变量,这就相当于C++的成员变量。

C结构没有像"public","protected"或"private"等的访问层级修饰,一般的方法是借着在实体结构里提供一个指向私有资料的指针,照惯例称作_priv。私有的结构可以宣告在公有的头文件里,然后把实体的定义写在实现的文件中,这样作,对用户来说,他并不知道私有资料是什么,但对于实现者来说,却可以很清楚的知道。如果私有结构也注册在GType里,那么对象系统将会自动帮它配置空间。

GObject框架最主要的不利点在于太过于冗长。像是手动定义的类型转换宏和难解的类型注册咒语等的大量模板代码都是建立新类别所必要的。GObject Builder或GOB2这些工具试图以提供模板语法来解决这个问题。以GOB2写的代码必须事先处理过才能编译。另外Vala可以将c# 的语法转换成C,并编译出独立的二进制档。


最新版本:2.0


项目主页:https://docs.gtk.org/gobject/