GObject

GLib对象系统,或者说 GObject,是一个在 LGPL下发布的 自由 软件库 ,它提供了一个轻便的 对象系统并支持透明的多语言互通。GObject被设计为可以直接使用在 C 程序中,也 封装至其他语言。

历史

仅依赖于GLib和libc,GObject是GNOME的基石并且普遍使用在GTK+, Pango,Accessibility Toolkit和大多数GNOME的高级库和应用程序中 。在GTK+ 2.0之前,GObject代码是GTK+的一部分。(现在GObject这个名字已经不在GTK+中了──取代它的基本类型叫做GtkObject。)

事实上,GTK+ 2.0 有自己的物件系统(GtkObject),作为 GTK+ 2.0 的使用。GtkObject 在 Gtk 演进的过程里,大部分与 GUI 不相关的部份都移到了 GObject 里,造就了新的共用基础类别 GObject 的诞生。GObject 以一个分离的函式库存在的时间是在 2002 年 3 月 11 日也就是 GTK+ 2.0 释出的日子,GObject 函数库现在被许多非 GUI 的程式,像指令列应用程式、服务器应用程式等,所使用。

与GLib的关系

虽然 GObject 有属于它自己的文件 [1] 与独立的函式库,但源代码却是在 Glib 的源码树里,并且与 GLib 一起发行。因为这样,GObject 使用 GLib 的版本号码并且一般 Linux Distro 的作法也是包在 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)可能就需要撰写和(或)复制上百行的代码。虽然如此,不可否认地,GObject 可以为 C 的代码提供面向对象的功能。

GObject 应用程式在执行时期为类别和接口所建立的元物件提供了互相操作的良好支援。可互相操作的能力被使用在语言系结上,还有使用者接口设计应用程式(如Glade)上。Glade 允许加载提供放了Widget(元件,衍生自GObject)的函式库,并且取得类别的属性列表、类型资讯以及文件字串。

与其他物件系统比较

GObject 为 C 提供了完整的物件系统,跟其他从 C 分支出去的语言,像 C++ 和 Objective-C 比起来,其实有很大的不同。先不管 C++ 其实有很多其他物件系统的特征,最明显也最简单的不同,是 GObject 跟 Java 一样,不支援多重继承。

另一个重要的不同,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,语言,编译器,Signal,gtk,Closures)