GObject
GLib对象系统,或者说GObject,是一个在LGPL下发布的自由软件库,它提供了一个轻便的对象系统并支持透明的多语言互通。GObject被设计为可以直接使用在C程序中,也可以被封装至其他语言,例如C++,Java,Python,以及可以生成C代码的Vala(由此大大简化了GObject代码的书写)等等。
开发者 | GNOME计划 |
---|---|
首次发布 | 2002年3月11日 |
当前版本 |
|
编程语言 | C |
操作系统 | 跨平台 |
类型 | 软件库 |
许可协议 | GNU LGPL |
网站 | developer |
历史
GObject仅依赖于GLib和libc。它是GNOME的基石并且在GTK+、Pango、辅助功能工具箱,和大多数GNOME的高级库和应用程式中被广泛使用。在GTK+ 2.0之前,GObject代码是GTK+的一部分。(现在GObject这个名字已经不在GTK+中了──取代它的基本类型叫做GtkObject
。)
由于对象系统适用的范围更广,较有一般性,所以在GTK+2.0发布时,对象系统被分离了出来,改放到了另一个函数库。GtkObject在Gtk演进的过程里,大部分与GUI不相关的部分都移到了GObject里,造就了新的共享基础类型GObject的诞生。从2002年3月11日(也就是GTK+ 2.0发布的日子)开始,GObject就一直以一个分离的函数库的形式存在。GObject函数库现在被许多非GUI的程序,像命令行应用程式、伺服器应用程式等,所使用。
与GLib的关系
虽然GObject有属于它自己的文档集[1],与独立的函数库,但原始码却是在Glib的原始码树里,并且与GLib一起发行。因此,GObject使用与GLib一样的版本号码,而且一般Linux Distro的作法也是把GObject包在GLib里(举例来说,Debian把GObject放在libglib2.0包家族里)。
类型系统
GObject框架的基层主要依靠泛型与动态类型,称作GType。GType系统保留所有物件的执行时期描述以让glue code能方便地与其他语言作链接。类型系统可以处理任何单一继承的类别架构以及非类别的类型,如不透明指针、字符串跟各种长度的整数与浮点数。
类型系统知道如何复制、指派和释放属于任何已注册类型的值。这对像整数这种不是引用计数(reference-counted)的类型来说是很琐碎的事情,但是对于其他是引用计数的复杂物件来说,是必要的。当类型系统复制了一个引用计数的物件,它仅仅只是增加该物件的引用计数,对复制一个复杂、不是引用计数的物件时,则是以配置存储器的方式建立副本。
这项基本的能力被实现在GValue,GValue是一个泛型容器的类型,里面可以用来保存注册在类型系统里的类型的值。这样的容器在与动态类型语言沟通时,特别地有用。
基础类型
没有任何关系类别的类型被称作非类别的。这些类型相当于根类别,也就是基础类型,可以被其他类型继承。
在GLib 2.9.2里[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基础类型派生下来的,主要有四种类型:
- 枚举类型和"旗标"类型
- 一般来说,枚举类型或旗标其实都是整数,以相对口语的单字来表示特定的数值,一般都作为物件属性的类型。GLib提供了
glib-mkenums
工具[3],来自动化产生的过程,并产生必要的代码。 - Boxed类型
- 有些数据结构很简单,并不需要是一个类别。举例来说,我们可能有个类别,里面需要加个
background-color
属性,他的值应该是某个结构的实体,所以代码看起来像是这样:struct color { int r,g,b; }
。要避免继承GObject
的话,我们可以建立一个boxed类型来呈现这样的结构,并且提供复制和释放的处理函数给GType。GObject提供了简便的方法,可以让你为GLib数据类型作包装。 - 不透明的指针类型(Opaque pointer types)
- 有时候,物件既不需要复制也不需要作引用计数或释放。这样的物件在GObject里,可以当作是不透明指针(
G_TYPE_POINTER
)。通常被用来参考到特定种类的物件。 - 类别与接口类型
- GObject应用程式里的大部分类型都是类别,直接或间接地继承自根类别:
GObject
。是的,GObject里也可以使用类似Java的接口(interface),虽然很少被使用到。很少使用到的原因可能是因为接口是在GLib 2.4(在2004年3月16日发布)才被加进去。GIMP就有使用到GObject的接口。
消息系统
GObject消息系统由两个互补的部分所组成:closures与信号。
- Closures
- GObject closure是callback(回调)的一般化版本。支持现存已经用C/C++或其他语言写好的closure(当提供绑定时)。这允许以例如Python或Java等写好的代码被GObject closure调用。
- 信号
- 信号(Signal)是closure被调用的主要机制。物件向类型系统注册信号listener,在给定的信号与给定的closure间指定对应关系。当注册的信号被发出时,对应的closure就会被调用。在GTK+里,所有内定的GUI事件(像滑鼠移动和键盘动作)都会为listeners产生GObject信号以进行运作。
类别实现
每个GObject类别必须包含至少两个结构:类别结构与实体结构。
- 类别结构
- 类别结构相当于C++类的vtable。第一个元素必须是父类的类别结构。里面包含一组函数指针,也就是类别的虚拟方法。放在这里的变量,就像是C++类里的const或类别层级的成员。
- 实体结构
- 每个物件实体都将会是这个实体结构的副本,同样地,第一个元素,必须是实体结构的父类(这可以确保每个实体都有个指针可以指向类别结构,所有的基类也同样如此),在归入父类之后,可以在结构内放其他的变量,这就相当于C++的成员变量。
C结构没有像"public", "protected"或"private"等的存取层级修饰,一般的方法是借着在实体结构里提供一个指向私有资料的指针,照惯例称作_priv。私有的结构可以宣告在公有的头文件里,然后把实体的定义写在实现的文件中,这样作,对用户来说,他并不知道私有资料是什么,但对于实现者来说,却可以很清楚的知道。如果私有结构也注册在GType里,那么物件系统将会自动帮它配置空间。
GObject框架最主要的不利点在于太过于冗长。像是手动定义的类型转换宏和难解的类型注册咒语等的大量模板代码都是建立新类别所必要的。GObject Builder或GOB2这些工具试图以提供模板语法来解决这个问题。以GOB2写的代码必须事先处理过才能编译。另外,Vala可以将c# 的语法转换成C,并编译出独立的二进制档。
用途
C与GObject的组合被使用在许多成功的自由软件项目上,像是GNOME桌面、GTK与GIMP影像处理软件。
尽管许多的GObject应用程式完全以C来撰写,但GObject系统可以很好地对应到许多语言,像C++、Java、Ruby、Python和.NET/Mono等的原生物件系统。所以在为已经使用GObject框架写好的函数库建立语言绑定时,通常比较不会那么痛苦。
以C来撰写GObject代码时,却是相对痛苦。学习曲线十分陡峭,有高阶面向对象语言经验的开发者可能会发现以C撰写GObject代码相当的乏味。举例来说,要继承一个类别(即使是继承GObject)可能就需要撰写和(或)复制上百行的样板代码[4]。虽然如此,不可否认地,GObject可以为C的代码提供面向对象的功能。
GObject应用程式在执行时期为类别和接口所建立的元物件提供了互相操作的良好支持。可互相操作的能力被使用在语言绑定上,还有用户界面设计应用程式(如Glade)上。Glade允许加载提供放了Widget(组件,派生自GObject)的函数库,并且获取类别的属性列表、类型资讯以及文件字符串。
与其他物件系统比较
GObject为C提供了近乎完整的物件系统,所以使用C语言配合GObject,可以做为使用其他从C分支出去的语言,像C++和Objective-C,的替代方案。(不过C++和Objective-C其实各自有很多其他特色,而不是只有差在物件系统。) 最明显也最简单的不同,是GObject跟Java一样,不支持多重继承[5]。
另一个重要的不同,GObject仅仅只是一个函数库,而C++和Objective-C的编译器还额外提供了新的语法或特性。
就目前的C++编译器来说,并没有标准的ABI可以在所有的C++编译器运行(除了Windows,Windows上有COM可以处理),以A这个C++编译器所编译出来的函数库,并不一定能被以B C++编译器所编译的程序调用。如果需要这样的兼容性,C++的方法必须要输出为C的函数,这样就无法享受C++带来的好处了。这主要是因为不同的C++编译器使用了不同的名称特殊处理(Name mangling)以确保输出符号的独一性。(这是必要的,举例来说,不同的类别可能会有一样名称的成员函数、被覆载许多次的函数名称,或者出现在多个命名空间但同名的函数,但在输出为目标文件时,这些代码都是独立的,如果名称都一样,会被视为同一份代码,因此需要对名称作特殊处理。)对照C来看,C不支持任何形式的覆载或名称特殊处理,C函数库的作者永远只能使用明确的前置名以确保输出名称的全局独一性。因此,以C撰写的GObject基底的函数库将不会有名称特殊处理的问题,也不会受到编译器的影响。
最后,"信号"(signal)可以说是更容易被发现的相异点了(在其他语言被称作事件)。这当然是因为GObject最早被设计用在GUI工具箱上。的确,许多面向对象语言都已经有现成的信号函数库,但GObject是被内建在物件系统里的。因此,GObject应用程式与其他非GObject应用程式比起来,会倾向于去使用信号来实现。这使得GObject 组件,相较于纯C++或Java写的组件,更易于封装,也容易被重复使用。不过该注意的是,在几乎所有的平台都可以使用信号,但有时其实需要额外的函数库支持,例如可用于C++的Boost.Signals2。
引用
- ^ GObject Reference Manual. [2022-03-07]. (原始内容存档于2021-04-20).
- ^ GObject Reference Manual - Stable. [2022-03-07]. (原始内容存档于2021-04-20).
- ^ glib-mkenums, GObject Reference Manual. [2022-03-07]. (原始内容存档于2021-04-20).
- ^ How to define and implement a new GObject. gnome.org. [27 July 2013]. (原始内容存档于2021-04-18).
- ^ c++ - Why Was the GObject System Created?. Stack Overflow. [2019-11-16]. (原始内容存档于2022-03-07).