目录
不可实例化的基本类型 (如gchar)
接口销毁
引用计数
弱引用
引用计数和周期
背景
GObject和它的低级类型系统GType被GTK +和大多数GNOME库用来提供:
-
面向对象的基于C的API和
-
自动透明API绑定到其他编译或解释语言。
许多程序员习惯于使用仅编译语言或动态解释语言,并且不了解与跨语言互操作性相关的挑战。本简介旨在提供对这些问题的见解,并简要介绍GLib选择的解决方案。
以下章节将详细介绍GType和GObject如何工作。从其他解释语言访问C对象是主要的设计目标之一:这通常可以解释此库中存在的有时相当复杂的API和功能。
数据类型和编程
可以说编程语言只是一种创建数据类型和操作它们的方法。大多数语言提供了许多语言基本类型,以基于这些基本类型创建更复杂的类型。
在C语言中,语言提供char,long, pointer等类型。在编译C代码期间,编译器将这些语言类型映射到编译器的目标体系结构机器类型。如果您正在使用C解释器,则解释器(解释源代码并执行它的程序)在程序执行期间(或之前)将语言类型映射到目标机器的机器类型执行(如果它使用Just In Time编译器引擎,则在执行之前)。
Perl和Python是解释性语言,它们并不提供与C语言类似的类型定义。Perl和Python程序员操纵变量,变量的类型仅在第一次赋值时或第一次使用时决定。解释器经常提供从一种类型到另一种类型的大量自动转换。例如,在Perl中,保存整数的变量可以自动转换为给定所需上下文的字符串:
my $tmp = 10;
print "this is an integer converted to a string:" . $tmp . "\n";
当然,当语言提供的默认转换不直观时,通常也可以明确指定转换。
导出C API
C API由一组函数和全局变量定义,这些函数和全局变量通常从二进制文件中导出。C函数具有任意数量的参数和一个返回值。因此,每个函数由函数名称和描述函数参数和返回值的C类型集唯一地标识。API导出的全局变量同样通过其名称和类型来标识。
因此,C API仅由一组与一组类型相关联的名称定义。如果您知道函数调用约定以及C类型到您所在平台使用的机器类型的映射,则可以解析每个函数的名称以查找与此函数关联的代码在内存中的位置,然后构造函数的有效参数列表。最后,您所要做的就是使用参数列表触发对目标C函数的调用。
为了便于讨论,下面是一个示例C函数以及GCC在Linux计算机上生成的相关32位x86汇编代码:
static void
function_foo (int foo)
{
}
int
main (int argc,
char *argv[])
{
function_foo (10);
return 0;
}
push $0xa
call 0x80482f4 <function_foo>
上面显示的汇编代码非常简单:第一条指令将十六进制值0xa(十进制值10)作为32位整数推送到堆栈并调用 function_foo
。如您所见,C函数调用由GCC实现为本机函数调用(这可能是最快的实现)。
现在,假设我们想function_foo
从Python程序中调用C函数。为此,Python解释器需要:
-
查找功能所在的位置。这可能意味着找到导出此函数的C编译器生成的二进制文件。
-
在可执行内存中加载函数的代码。
-
在调用函数之前,将Python参数转换为C兼容参数。
-
使用正确的调用约定调用该函数。
-
将C函数的返回值转换为Python兼容的变量,以将它们返回到Python代码。
上面描述的过程非常复杂,有很多方法可以使C和Python程序员完全自动化和透明化:
-
第一个解决方案是手动编写大量的粘合代码,每个导出或导入的函数各一次,它执行Python-to-C参数转换和C-to-Python返回值转换。然后将此粘合代码与解释器链接,该解释器允许Python程序调用“将函数委派给C函数”的Python函数。
-
另一个更好的解决方案是使用一个读取原始函数签名的特殊编译器为导出或导入的每个函数自动生成粘合代码。
-
GLib使用的解决方案是使用GType库,该库在运行时保存程序员操纵的所有对象的描述。然后,这个所谓的动态类型 [1] 库由特殊的通用粘合代码使用,以自动转换不同运行时域之间的函数参数和函数调用约定。
GType实现的解决方案的最大优点是位于运行时域边界的粘合代码只写一次:下图更清楚地说明了这一点。
图1。
目前,至少存在Python和Perl通用粘合代码,这使得可以直接在Python或Perl中使用用GType编写的C对象,只需要最少的工作量:无需自动或手动生成大量的胶水代码。
虽然这个目标可以说是值得称道的,但它的追求对整个GType / GObject库产生了重大影响。GType / GObject库不仅旨在为C程序员提供类似OO的功能,而且还提供透明的跨语言互操作性,如果不知道这一特点,那么C程序员可能会对下面章节中公开的功能的复杂性感到困惑。
[1] 动态类型系统有许多不同的实现:所有C ++编译器都有一个,Java和.NET也有一个。动态类型系统允许您在运行时获取有关每个实例化对象的信息。它可以由特定于进程的数据库实现:创建的每个新对象都在类型系统中注册其关联类型的特征。它也可以通过内省接口实现。所有这些不同类型系统和实现之间的共同点是它们都允许您在运行时查询对象元数据。
GLib动态类型系统
接口销毁
由GLib类型系统操纵的类型比通常理解为对象类型的类型更通用。最好通过参考“用于在类型系统中注册新类型的结构和函数“。
typedef struct _GTypeInfo GTypeInfo;
struct _GTypeInfo
{
/* interface types, classed types, instantiated types */
guint16 class_size;
GBaseInitFunc base_init;
GBaseFinalizeFunc base_finalize;
/* classed types, instantiated types */
GClassInitFunc class_init;
GClassFinalizeFunc class_finalize;
gconstpointer class_data;
/* instantiated types */
guint16 instance_size;
guint16 n_preallocs;
GInstanceInitFunc instance_init;
/* value handling */
const GTypeValueTable *value_table;
};
GType g_type_register_static (GType parent_type,
const gchar *type_name,
const GTypeInfo *info,
GTypeFlags flags);
GType g_type_register_fundamental (GType type_id,
const gchar *type_name,
const GTypeInfo *info,
const GTypeFundamentalInfo *finfo,
GTypeFlags flags);
g_type_register_static
, g_type_register_dynamic和
g_type_register_fundamental
是C函数,在gtype.h中定义,并
在gtype.c中
实现,您应该在程序的类型系统中注册新的GType。您可能不需要使用 g_type_register_fundamental
,但如果您愿意,最后一章将介绍如何创建新的基本类型。
基本类型是顶级类型,不是从任何其他类型派生的。在初始化时,类型系统不仅初始化其内部数据结构,而且还注册了许多核心类型:其中一些是基本类型。其他是从这些基本类型派生的类型。
基本类型和非基本类型定义如下:
-
类大小:GTypeInfo中的class_size字段。
-
类初始化函数(C ++构造函数):GTypeInfo中的
base_init
和class_init
字段。 -
类功能的析构(C ++析构):GTypeInfo的base_finalize和class_finalize字段。
-
实例大小(C++ parameter to new):GTypeInfo中的instance_size字段 。
-
实例化策略(C++ type of new operator):GTypeInfo的n_preallocs字段。
-
复制函数(C++ copy operators):在value_table字段 GTypeInfo。
-
类型特征标志:GTypeFlags。
基本类型也由一组定义GTypeFundamentalFlags,存储在一个GTypeFundamentalInfo。非基本类型还由它们的父类型定义,它作为parent_type参数传递给g_type_register_static
和g_type_register_dynamic
。
复制功能
所有 GLib类型(基本和非基础,classed and non-classed,可实例化和不可实例化) 之间的主要共同点是,它们都可以通过单个API进行操作来复制/分配它们。
所述的GValue结构被用作用于所有这些类型的抽象容器。其简单的API(在gobject/gvalue.h
中定义)可调用在类型注册期间注册的value_table函数:例如g_value_copy
,将GValue的内容复制到另一个GValue。这类似于C ++赋值,它调用C ++复制运算符来修改C ++ / C结构/类的默认逐位复制语义。
以下代码显示了如何复制64位整数以及GObject 实例指针:
static void test_int (void)
{
GValue a_value = G_VALUE_INIT;
GValue b_value = G_VALUE_INIT;
guint64 a, b;
a = 0xdeadbeef;
g_value_init (&a_value, G_TYPE_UINT64);
g_value_set_uint64 (&a_value, a);
g_value_init (&b_value, G_TYPE_UINT64);
g_value_copy (&a_value, &b_value);
b = g_value_get_uint64 (&b_value);
if (a == b) {
g_print ("Yay !! 10 lines of code to copy around a uint64.\n");
} else {
g_print ("Are you sure this is not a Z80 ?\n");
}
}
static void test_object (void)
{
GObject *obj;
GValue obj_vala = G_VALUE_INIT;
GValue obj_valb = G_VALUE_INIT;
obj = g_object_new (VIEWER_TYPE_FILE, NULL);
g_value_init (&obj_vala, VIEWER_TYPE_FILE);
g_value_set_object (&obj_vala, obj);
g_value_init (&obj_valb, G_TYPE_OBJECT);
/* g_value_copy's semantics for G_TYPE_OBJECT types is to copy the reference.
* 因此该函数调用g_object_ref。
* 值得注意的是,赋值在此处起作用,
* 因为VIEWER_TYPE_FILE是G_TYPE_OBJECT。
*/
g_value_copy (&obj_vala, &obj_valb);
g_object_unref (G_OBJECT (obj));
g_object_unref (G_OBJECT (obj));
}
关于上述代码的重点是复制调用的确切语义是未定义的,因为它们依赖于复制函数的实现。某些复制功能可能决定分配新的内存块,然后将数据从源复制到目标。其他人可能希望简单地增加实例的引用计数并将引用复制到新的GValue。
用于指定这些赋值函数的值表记录在 GTypeValueTable中。
有趣的是,在类型注册期间,您也不太可能需要指定value_table,因为这些value_tables是从非基本类型的父类型继承的。
约定
在创建要在头文件中导出的新类型时,用户应遵循许多约定:
-
类型名称(包括对象名称)必须至少包含三个字符,并以“a-z”,“A-Z”或“_”开头。
-
使用
object_method
模式进行函数名称:调用在save
对象类型文件的实例上调用的方法,调用file_save
。 -
使用前缀来避免命名空间与其他项目冲突。如果您的库(或应用程序)名为Viewer,则使用viewer_为您的所有函数名称添加前缀。例如:
viewer_object_method
。 -
创建一个名为
PREFIX_TYPE_OBJECT
的宏,它始终返回关联对象类型的GType。在Viewer中的文件类型对象,命名为VIEWER_TYPE_FILE
。该宏使用名为prefix_object_get_type
的函数实现 ; 例如viewer_file_get_type
。 -
使用
G_DECLARE_FINAL_TYPE
或G_DECLARE_DERIVABLE_TYPE
为您的对象定义各种其他传统宏:-
PREFIX_OBJECT (obj)
,返回PrefixObject类型的指针。此宏用于进行显式强制转换来保证静态类型的安全性。它还通过执行运行时检查来保证动态类型的安全性。可以在构建中禁用动态类型检查(请参阅构建GLib)。例如,我们将创建VIEWER_FILE (obj)
以保留前面的示例。 -
PREFIX_OBJECT_CLASS (klass)
,它严格等同于前一个转换宏:它使用动态类型检查类结构进行静态转换。返回指向PrefixObjectClass类型的类结构的指针。例如:VIEWER_FILE_CLASS
。 -
PREFIX_IS_OBJECT (obj)
,返回一个gboolean,指示输入对象实例指针是非NULL还是OBJECT类型。例如,VIEWER_IS_FILE
。 -
PREFIX_IS_OBJECT_CLASS (klass)
,如果输入类指针是指向OBJECT类的类的指针,则返回布尔值。例如,VIEWER_IS_FILE_CLASS
。 -
PREFIX_OBJECT_GET_CLASS (obj)
,返回与给定类型的实例关联的类指针。此宏用于静态和动态类型安全目的(就像之前的构建宏一样)。例如,VIEWER_FILE_GET_CLASS
。
-
这些宏的实现非常简单:提供了许多简单易用的宏gtype.h
。对于我们上面使用的示例,我们将编写以下简单的代码来声明宏:
#define VIEWER_TYPE_FILE viewer_file_get_type ()
G_DECLARE_FINAL_TYPE (ViewerFile, viewer_file, VIEWER, FILE, GObject)
除非您的代码有特殊要求,否则您可以使用 G_DEFINE_TYPE
宏来定义类:
G_DEFINE_TYPE (ViewerFile, viewer_file, G_TYPE_OBJECT)
否则,viewer_file_get_type
必须手动实现该功能:
GType viewer_file_get_type (void)
{
static GType type = 0;
if (type == 0) {
const GTypeInfo info = {
/* You fill this structure. */
};
type = g_type_register_static (G_TYPE_OBJECT,
"ViewerFile",
&info, 0);
}
return type;
}
不可实例化的基本类型
许多类型不能通过类型系统实例化,也没有类。这些类型中的大多数是基本的普通类型,例如gchar,并且已经由GLib注册。
在需要在类型系统中注册这种类型的极少数情况下,用零填充 GTypeInfo结构:
GTypeInfo info = {
0, /* class_size */
NULL, /* base_init */
NULL, /* base_destroy */
NULL, /* class_init */
NULL, /* class_destroy */
NULL, /* class_data */
0, /* instance_size */
0, /* n_preallocs */
NULL, /* instance_init */
NULL, /* value_table */
};
static const GTypeValueTable value_table = {
value_init_long0, /* value_init */
NULL, /* value_free */
value_copy_long0, /* value_copy */
NULL, /* value_peek_pointer */
"i", /* collect_format */
value_collect_int, /* collect_value */
"p", /* lcopy_format */
value_lcopy_char, /* lcopy_value */
};
info.value_table = &value_table;
type = g_type_register_fundamental (G_TYPE_CHAR, "gchar", &info, &finfo, 0);
具有不可实例化的类型可能看起来有点无用:如果您无法实例化该类型的实例,那么类型有什么用呢?大多数这些类型与GValue一起使用:GValue用整数或字符串初始化,并使用注册类型的value_table传递。当与对象属性和信号结合使用时,GValue(以及扩展这些简单的基本类型)最有用。
可实例化的类型:对象
本节介绍对象背后的理论。有关定义GObject的推荐方法,请参见 如何定义和实现新的GObject。
在类中注册并声明为可实例化的类型最接近于对象。尽管GObject(在GObject基类中有详细介绍)是最有名的可实例化类型,但是其他类似的对象用作继承层次结构的基础已经在外部开发,它们都建立在下面描述的基本特征上。
例如,下面的代码显示了如何在类型系统中注册这样一个基本对象类型(不使用GObject的API):
typedef struct {
GObject parent;
/* instance members */
gchar *filename;
} ViewerFile;
typedef struct {
GObjectClass parent;
/* class members */
/* the first is public, pure and virtual */
void (*open) (ViewerFile *self,
GError **error);
/* the second is public and virtual */
void (*close) (ViewerFile *self,
GError **error);
} ViewerFileClass;
#define VIEWER_TYPE_FILE (viewer_file_get_type ())
GType
viewer_file_get_type (void)
{
static GType type = 0;
if (type == 0) {
const GTypeInfo info = {
sizeof (ViewerFileClass),
NULL, /* base_init */
NULL, /* base_finalize */
(GClassInitFunc) viewer_file_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (ViewerFile),
0, /* n_preallocs */
(GInstanceInitFunc) NULL /* instance_init */
};
type = g_type_register_static (G_TYPE_OBJECT,
"ViewerFile",
&info, 0);
}
return type;
}
在第一次调用时viewer_file_get_type
,名为ViewerFile的类型 将在类型系统中注册为继承自类型G_TYPE_OBJECT。
每个对象必须定义两个结构:它的类结构和它的实例结构。所有类结构必须包含GTypeClass结构的第一个成员。所有实例结构必须包含GTypeInstance结构作为第一个成员。这些C类型的声明gtype.h
如下所示:
struct _GTypeClass
{
GType g_type;
};
struct _GTypeInstance
{
GTypeClass *g_class;
};
这些约束允许类型系统确保每个对象实例(由指向对象实例结构的指针标识)在其第一个字节中包含指向对象类结构的指针。
这个关系最好用一个例子来解释:让我们从对象A继承对象B:
/* A definitions */
typedef struct {
GTypeInstance parent;
int field_a;
int field_b;
} A;
typedef struct {
GTypeClass parent_class;
void (*method_a) (void);
void (*method_b) (void);
} AClass;
/* B definitions. */
typedef struct {
A parent;
int field_c;
int field_d;
} B;
typedef struct {
AClass parent_class;
void (*method_c) (void);
void (*method_d) (void);
} BClass;
C标准规定C结构的第一个字段从缓冲区的第一个字节开始存储,用于保存内存中结构的字段。这意味着对象B的实例的第一个字段是A的第一个字段,而第一个字段又是GTypeInstance的第一个字段,而第一个字段又是g_class
指向B类结构的指针。
由于这些简单的条件,可以通过执行以下操作来检测每个对象实例的类型:
B *b;
b->parent.parent.g_class->g_type
或者,更快:
B *b;
((GTypeInstance *) b)->g_class->g_type
初始化和销毁
可以使用 g_type_create_instance
来实例化这些类型,查找与所请求类型相关联的类型信息结构。然后,实例大小和实例化策略(如果n_preallocs
字段设置为非零值,类型系统以块为单位分配对象的实例结构而不是每个实例的mallocing)用于获取缓冲区以保存对象的实例结构。
如果这是有史以来创建的对象的第一个实例,则类型系统必须创建类结构。它分配一个缓冲区来保存对象的类结构并初始化它。类结构的第一部分(即:嵌入的父类结构体)通过从父类的类结构复制内容来初始化。类结构的其余部分初始化为零。如果没有父级,则将整个类结构初始化为零。然后类型系统从最顶层的基础对象调用 base_class_initialization
函数(GBaseInitFunc)到最底层的派生对象。对象的class_init
(GClassInitFunc)之后调用函数来完成类结构的初始化。最后,初始化对象的接口(稍后我们将更详细地讨论接口初始化)。
一旦类型系统有一个指向初始化类结构的指针,它就会将对象的实例类指针设置为对象的类结构,并调用对象的 instance_init
(GInstanceInitFunc)函数,从最顶层的基本类型到最底层的派生类型。
对象实例的析构g_type_free_instance
非常简单:如果有实例结构,则将实例结构返回给实例池,如果这是对象的最后一个实例,则该类将被销毁。
类析构(析构的概念有时部分称为GType中的终结)是初始化的对称过程:接口首先被破坏。然后,调用派生最多的class_finalize(GClassFinalizeFunc)函数。最后,从最底层的派生类型到最顶层的基本类型调用base_class_finalize(GBaseFinalizeFunc)函数,并释放类结构。
基本初始化/完成过程与C ++构造函数/析构函数范例非常相似。实际细节虽然不同,但重要的是不要被肤浅的相似性弄糊涂。GType没有实例销毁机制。用户有责任在现有GType代码之上实现正确的销毁语义。(这是不GObject的:见 GObject的基类。)另外,C ++代码等同于base_init
和class_init
一般不需要的GType的回调因为C ++不能真正在运行时创建的对象类型。
实例化/完成过程可归纳如下:
表1. GType实例化/完成
调用时间 | 调用函数 | 功能参数 |
---|---|---|
首先调用g_type_create_instance 目标类型 | 类型的base_init 功能 | 关于从基本类型到目标类型的类的继承树。 base_init 为每个类结构调用一次。 |
目标类型的class_init 功能 | 关于目标类型的类结构 | |
接口初始化,请参阅 “接口初始化”一节 | ||
每次调用g_type_create_instance 目标类型 | 目标类型的instance_init 功能 | 在对象的实例上 |
最后一次调用g_type_free_instance 目标类型 | 接口销毁,请参阅 “接口销毁”一节 | |
目标类型的class_finalize 功能 | 关于目标类型的类结构 | |
类型的base_finalize 功能 | 关于从基本类型到目标类型的类的继承树。 base_finalize 为每个类结构调用一次。 |
不可实例化的分类类型:接口
本节介绍了接口背后的理论。有关定义接口的建议方法,请参阅 如何定义和实现接口。
GType的接口与Java的接口非常相似。它们允许描述几个类将遵循的通用API。想象一下高保真设备上的播放,暂停和停止按钮 - 可以看作是播放界面。一旦你知道他们做了什么,你就可以控制你的CD播放器,MP3播放器或任何使用这些符号的东西。要声明一个接口,您必须注册一个从GTypeInterface派生的非可实例化的类型 。以下代码声明了这样的接口。
#define VIEWER_TYPE_EDITABLE viewer_editable_get_type ()
G_DECLARE_INTERFACE (ViewerEditable, viewer_editable, VIEWER, EDITABLE, GObject)
struct _ViewerEditableInterface {
GTypeInterface parent;
void (*save) (ViewerEditable *self,
GError **error);
};
void viewer_editable_save (ViewerEditable *self,
GError **error);
接口函数viewer_editable_save
以非常简单的方式实现:
void
viewer_editable_save (ViewerEditable *self,
GError **error)
{
ViewerEditableinterface *iface;
g_return_if_fail (VIEWER_IS_EDITABLE (self));
g_return_if_fail (error == NULL || *error == NULL);
iface = VIEWER_EDITABLE_GET_IFACE (self);
g_return_if_fail (iface->save != NULL);
iface->save (self);
}
viewer_editable_get_type
注册一个名为ViewerEditable的类型 ,它继承自G_TYPE_INTERFACE。所有接口必须是继承树中G_TYPE_INTERFACE的子级。
接口仅由一个结构定义,该结构必须包含GTypeInterface结构作为第一个成员。接口结构应该包含接口方法的函数指针。为每个简单地直接调用接口方法的接口方法定义辅助函数是一种很好的方式:viewer_editable_save
就是其中之一。
如果没有特殊要求,可以使用 G_IMPLEMENT_INTERFACE宏来实现接口:
static void
viewer_file_save (ViewerEditable *self)
{
g_print ("File implementation of editable interface save method.\n");
}
static void
viewer_file_editable_interface_init (ViewerEditableInterface *iface)
{
iface->save = viewer_file_save;
}
G_DEFINE_TYPE_WITH_CODE (ViewerFile, viewer_file, VIEWER_TYPE_FILE,
G_IMPLEMENT_INTERFACE (VIEWER_TYPE_EDITABLE,
viewer_file_editable_interface_init))
如果您的代码确实有特殊要求,那么您必须编写一个自定义 get_type
函数来注册从某些GObject继承 并实现ViewerEditable接口的GType。例如,此代码注册了一个实现ViewerEditable的新ViewerFile类:
static void
viewer_file_save (ViewerEditable *editable)
{
g_print ("File implementation of editable interface save method.\n");
}
static void
viewer_file_editable_interface_init (gpointer g_iface,
gpointer iface_data)
{
ViewerEditableInterface *iface = g_iface;
iface->save = viewer_file_save;
}
GType
viewer_file_get_type (void)
{
static GType type = 0;
if (type == 0) {
const GTypeInfo info = {
sizeof (ViewerFileClass),
NULL, /* base_init */
NULL, /* base_finalize */
NULL, /* class_init */
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (ViewerFile),
0, /* n_preallocs */
NULL /* instance_init */
};
const GInterfaceInfo editable_info = {
(GInterfaceInitFunc) viewer_file_editable_interface_init, /* interface_init */
NULL, /* interface_finalize */
NULL /* interface_data */
};
type = g_type_register_static (VIEWER_TYPE_FILE,
"ViewerFile",
&info, 0);
g_type_add_interface_static (type,
VIEWER_TYPE_EDITABLE,
&editable_info);
}
return type;
}
g_type_add_interface_static
在一个给定的类型也实现了型系统记录FooInterface (foo_interface_get_type
返回的类型 FooInterface)。该GInterfaceInfo结构保存有关接口的实现信息:
struct _GInterfaceInfo
{
GInterfaceInitFunc interface_init;
GInterfaceFinalizeFunc interface_finalize;
gpointer interface_data;
};
接口初始化
当第一次创建实现接口(直接或通过继承超类中的实现)的可实例化的分类类型时,其类结构按照“可实例化的分类类型:对象”一节中描述的过程进行初始化。之后,初始化与该类型相关联的接口实现。
首先,分配内存缓冲区以保存接口结构。然后将父接口结构复制到新接口结构(父接口已在该点初始化)。如果没有父接口,则使用零初始化接口结构。的 g_type
和 g_instance_type
字段然后初始化:g_type
设置为最衍生接口的类型和 g_instance_type
被设定为实现此接口的最派生类型的类型。
base_init
调用 接口的函数,然后default_init
调用接口。最后,如果类型已注册接口的实现,interface_init
则调用实现的函数。如果有多个接口实现,则每个初始化的实现都会调用一次base_init
和 interface_init
函数。
因此,建议使用default_init
函数初始化接口。无论有多少实现,该函数仅对接口调用一次。该 default_init
函数由G_DEFINE_INTERFACE声明 ,可用于定义接口:
G_DEFINE_INTERFACE (ViewerEditable, viewer_editable, G_TYPE_OBJECT)
static void
viewer_editable_default_init (ViewerEditableInterface *iface)
{
/* add properties and signals here, will only be called once */
}
或者你可以在你的界面的GType函数中自己做:
GType
viewer_editable_get_type (void)
{
static volatile gsize type_id = 0;
if (g_once_init_enter (&type_id)) {
const GTypeInfo info = {
sizeof (ViewerEditableInterface),
NULL, /* base_init */
NULL, /* base_finalize */
viewer_editable_default_init, /* class_init */
NULL, /* class_finalize */
NULL, /* class_data */
0, /* instance_size */
0, /* n_preallocs */
NULL /* instance_init */
};
GType type = g_type_register_static (G_TYPE_INTERFACE,
"ViewerEditable",
&info, 0);
g_once_init_leave (&type_id, type);
}
return type_id;
}
static void
viewer_editable_default_init (ViewerEditableInterface *iface)
{
/* add properties and signals here, will only called once */
}
总之,接口初始化使用以下功能:
表2.接口初始化
调用时间 | 调用的函数 | 功能参数 | 备注 |
---|---|---|---|
首先调用g_type_create_instance 的任何类型的接口实现 | 界面的base_init 功能 | 在界面的vtable上 | 很少需要使用它。每个实例化的类型调用一次实现接口。 |
第一次调用g_type_create_instance 为每个类型实现接口 | 界面的default_init 功能 | 在界面的vtable上 | 在此处注册接口的信号,属性等。将被召唤一次。 |
首先调用g_type_create_instance 的任何类型的接口实现 | 实现的interface_init 功能 | 在界面的vtable上 | 初始化接口实现。为实现接口的每个类调用。将接口结构中的接口方法指针初始化为实现类的实现。 |
接口销毁
当销毁注册接口实现的可实例化类型的最后一个实例时,将破坏与该类型关联的接口实现。
为了销毁接口实现,GType首先调用实现的interface_finalize
函数,然后调用接口的派生 base_finalize
函数。
再次,要了解,作为重要的是 所谓的“接口初始化”一节,双方interface_finalize
并base_finalize
正在为一个接口的每一次实施的破坏调用一次。因此,如果您要使用这些函数之一,则需要使用一个静态整数变量,该变量将保存接口实现的实例数,使得接口的类只被销毁一次(当整数变量达到零时) 。
以上过程可归纳如下:
表3.接口终结
调用时间 | 调用的函数 | 功能参数 |
---|---|---|
最后调用g_type_free_instance 类型实现接口 | 界面的interface_finalize 功能 | 在界面的vtable上 |
界面的base_finalize 功能 | 在界面的vtable上 |