GObject Note

初创建于: 2022-09-11 22:34

GObject

概述

GObject 可以理解为一个库, 使用这个库可以用 C 语言编写面向对象的程序.

这里通过一个例子直观地来理解一下 GObject.

定义类

使用 GObject 时, 变量的命名尽量遵循 <module>_<type> 的前缀, 如当前工程名为 T, 要定义一个类 Integer, 则将该类命名为 TInteger, 并对于各种函数以前缀 t_integer 命名.

首先, 在 GObject 中, 要定义一个「Class」需要两个结构体, 比如, 我要定义一个 TInteger 类, 则需要定义一个结构体 typedef struct _TInteger TIntegertypedef struct _TIntegerClass TIntegerClass.

这两者之间的关系比较复杂, 能力有限, 我难以描述清楚. 这里我们只关注如何使用, 而不深究其原理.

typedef _IntegerClass IntegerClass;
struct _IntegerClass {
	// 将 GObjectClass 作为其第一个成员, 代表继承自 GObject
	GObjectClass parent_class;
};

typedef _Integer Integer;
struct _Integer {
	// 将 GObject 作为其第一个成员, 代表继承自 GObject
	GObject parent;
	// 公开的成员变量
	int sex;
};

添加 private 属性

要为一个类添加 private 属性, 需要如下方法:

首先定义一个 TIntegerPrivate 类, 并将所有要设置为 private 的属性放到这里面

typedef struct _TIntegerPrivate TIntegerPrivate;
struct _TIntegerPrivate {
	int value;
};

需要注意的是, GObject 系统添加 private 属性的原理大概是, 在申请每一个类实例的空间时, 额外申请一块 private 的空间并“挂”到实例的空间旁边.

GObject 系统 private 属性的大小有 64K 的限制. 这一限制在 vala 中也有, 因为实际上 vala 编译器就是将 vala 代码“编译”为使用 glib 等实现的 C 语言代码. 因此 vala 中私有属性也无法避免 64K 大小的限制.

注册类型

无论是否添加 private 属性, 在定义了 TInteger, TIntegerClass 以及可能还有 TIntegerPrivate 之后, 都需要向 GObject 系统注册该类.

首先, 需要定义宏:

#define T_TYPE_INTEGER (t_integer_get_type())

如果没有定义 private 属性, 则可以:

// G_TYPE_OBJECT 与 T_TYPE_INTEGER 类似, 是属于 Integer 类父类的
G_DEFINE_TYPE (TInteger, t_integer, G_TYPE_OBJECT);

如果定义了 private 属性, 则可以:

G_DEFINE_TYPE_WITH_CODE (TInteger, t_integer, G_TYPE_OBJECT, G_ADD_PRIVATE(TInteger));

该宏会定义一个函数 TIntegerPrivate* t_integer_get_instance_private(TInteger*);, 该函数用于取出其私有属性.

通常将该部分内容写到一个单独的 c 文件中, 来对外隐藏 private 的部分.

初始化

在注册类型之后, G_DEFINE_TYPE* 宏会声明两个函数 : static void t_integer_init(TInteger*);static void t_integer_class_init(TIntegerClass*);

static void t_integer_init(TInteger* i) {
	TIntegerPrivate priv = t_integer_get_instance_private(i);
	priv->value = 0;
	// 如果没有 private, 则直接
	// i->value = 0;
}

一些特殊的宏

G_DECLARE_FINAL_TYPE

G_DECLARE_FINAL_TYPE (ModuleObjName, module_obj_name, MODULE, OBJ_NAME, ParentName)
/**
 * Example:
 * G_DECLARE_FINAL_TYPE (TDouble, t_double, T, Double, GObject);
 */

G_DECLARE_FINAL_TYPE 会做以下事情:

  1. 定义函数 <module>_<objName>_get_type(). 如 t_double_get_type().
  2. 声明了类型 typedef struct _<module><objName> <module><objName>typedef struct _<module><objName>Class <module><objName>Class. 如 typedef struct _TDouble TDoublestruct _TDoubleClass TDoubleClass.
  3. 定义宏 <module>_<objName>. 如 T_DOUBLE. 该宏会被展开成为一个函数, 该函数将一个 gpointer* 强制转换为 TDouble*.
  4. 定义宏 <module>_IS_<objName>. 如 T_IS_DOUBLE.

G_DECLARE_FINAL_TYPE 类似的还有 G_DECLARE_DERIVABLE_TYPE, 其区别在于 G_DECLARE_FINAL_TYPE 定义的类不可被继承, 而 G_DECLARE_DERIVABLE_TYPE 声明的类可以被继承, 即再产生子类.

G_DECLARE_*_TYPE 类宏与 G_DEFINE_TYPE* 类宏的区别在于, DECLARE 只是做了一些“体力活”, 即将一部分同质化的函数通过宏定义出来, 如 T_IS_INTEGER 等, 但是并没有向 GObject 系统注册该类, 而 DEFINE 类宏是向 GObject 类注册了该类.

一个例子

这里以 TInteger 作为一个例子.

t_integer.h :

#include <glib-object.h>

#ifndef __T_INTEGER_H_UCRSVKFT
#define __T_INTEGER_H_UCRSVKFT

#define T_TYPE_INTEGER (t_integer_get_type())

G_DECLARE_DERIVABLE_TYPE(TInteger, t_integer, T, INTEGER, GObject);

struct _TIntegerClass {
	GObjectClass parent_class;
};

TInteger*
t_integer_new(int value);

gboolean
t_integer_get_value(TInteger *self, int* value);

gboolean
t_integer_set_value(TInteger *self, int value);

#endif /* end of include guard: __T_INTEGER_H_UCRSVKFT */

t_integer.c :

#include "t_integer.h"

typedef struct _TIntegerPrivate TIntegerPrivate;
struct _TIntegerPrivate {
	int value;
};

G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(TInteger, t_integer, G_TYPE_OBJECT);

static void
t_integer_class_init(TIntegerClass* klass) { }

static void
t_integer_init(TInteger* self) {
	TIntegerPrivate* priv = t_integer_get_instance_private(self);
	priv->value = 0;
}

TInteger*
t_integer_new(int value) {
	TInteger* t_i = g_object_new(T_TYPE_INTEGER, NULL);
	TIntegerPrivate* priv = t_integer_get_instance_private(t_i);
	priv->value = value;
	return t_i;
}

gboolean
t_integer_get_value(TInteger *self, int* value) {
	g_return_val_if_fail(T_IS_INTEGER(self), FALSE);
	TIntegerPrivate* priv = t_integer_get_instance_private(self);
	*value = priv->value;
	return TRUE;
}

gboolean
t_integer_set_value(TInteger *self, int value) {
	g_return_val_if_fail(T_IS_INTEGER(self), FALSE);
	TIntegerPrivate* priv = t_integer_get_instance_private(self);
	priv->value = value;
	return TRUE;
}

信号机制

信号机制有点像 java 中的 ActionListener. 通过信号机制, 可以为某个实例注册一个信号, 当触发该信号时, 可以调用相应的回调函数.

信号注册 (Signal Registration)

在使用一个信号之前需先使用 g_signal_new 函数注册该信号:

guint g_signal_new(
	const gchar        *signal_name , /* 信号名                      */
	GType              itype        , /* 要为哪个类注册信号          */
	GSignalFlags       signal_flags , /* 信号的 Flag                 */
	guint              class_offset , /* 一个偏移量, 设为 0 即可     */
	GSignalAccumulator accumulator  , /* 暂时忽略这个参数            */
	gpointer           accu_data    , /* 暂时忽略这个参数, 设为NULL  */
	GSignalCMarshaller c_marshaller , /* 暂时忽略这个参数            */
	GType              return_type  , /* handler 函数的返回类型      */
	guint              n_params     , /* handler 函数接收的参数个数  */
	...                               /* 如果 n_params 是 0 则不需要 */
	)

例如:

#define T_INTEGER_SIGNAL_DIV_BY_ZERO "div-by-zero"
static guint t_integer_signal_div_by_zero;
static void
t_integer_class_init(TIntegerClass* klass) {
	t_integer_signal_div_by_zero =
		g_signal_new(
			T_INTEGER_SIGNAL_DIV_BY_ZERO ,
			G_TYPE_FROM_CLASS(klass),
			G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
			0,
			NULL,
			NULL,
			NULL,
			G_TYPE_NONE,
			0
		);
}

注意信号的名称有命名规则, 只能使用 ASCII 字符, 用短划线 "-" 或下划线 "_" 连接, 必须以字母开始, 建议使用短划线连接, 并且短划线与下划线不能混合使用.

信号处理 (Signal Handler)

定义如下函数:

static void callback_div_by_zero(TInteger* i, gpointer* user_data) {
	g_print("[ERROR] Div by ZERO!\n");
}

信号释放 (Signal Emission)

gboolean
t_integer_div(TInteger *a, TInteger *b, TInteger* result) {
	g_return_val_if_fail(T_IS_INTEGER(a) && T_IS_INTEGER(b) && T_IS_INTEGER(result), FALSE);

	int va, vb;
	gboolean ja = t_integer_get_value(a, &va);
	gboolean jb = t_integer_get_value(b, &vb);
	if (vb == 0) {
		g_signal_emit(b, t_integer_signal_div_by_zero, 0);
		return FALSE;
	}
	gboolean jr = t_integer_set_value(result, va / vb);
	return ja && jb && jr ? TRUE : FALSE;
}

信号链接 (Signal Connection)

void g_signal_connect(instance, detailed_signal, c_handler, data);

如 :

TInteger*
t_integer_new(int value) {
	TInteger* t_i = g_object_new(T_TYPE_INTEGER, NULL);
	TIntegerPrivate* priv = t_integer_get_instance_private(t_i);
	priv->value = value;
	g_signal_connect(t_i, T_INTEGER_SIGNAL_DIV_BY_ZERO, G_CALLBACK( callback_div_by_zero ), NULL);
	return t_i;
}

默认 Handler

有一些信号, 我们希望给他们默认的 Handler, 则可以使用 g_signal_new_class_handler 函数注册信号.
g_signal_new_class_handler 函数与 g_signal_new 函数的区别在于第四个参数, 在 g_signal_new 函数中, 第四个参数是一个偏移量, 用于在类中按照该偏移量寻找默认 handler 函数, 这样做的缺陷是对于 final 类, 无法在类中定义默认 handler 函数. 而 g_signal_new_class_handler 函数中第四个参数是一个函数, 用以指定为默认 handler.

static guint t_integer_signal_div_by_zero;
static void callback_div_by_zero(TInteger* i, gpointer* user_data); 

static void
t_integer_class_init(TIntegerClass* klass) {
	t_integer_signal_div_by_zero =
		g_signal_new_class_handler(T_INTEGER_SIGNAL_DIV_BY_ZERO ,
				G_TYPE_FROM_CLASS(klass),
				G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
				G_CALLBACK(callback_div_by_zero),
				NULL,
				NULL,
				NULL,
				G_TYPE_NONE,
				0);
}

在定义了默认 handler 后, 无需再对信号进行连接.

Flags

对于 g_signal_newg_signal_new_class_handler 函数中的第 3 个参数 signal_flags, 有如下选项:

Flag 含义
G_SIGNAL_RUN_FIRST 默认 handler 在所有用户定义 handler 之前运行
G_SIGNAL_RUN_LAST 默认 handler 在用户定义的正常 handler 之后运行 (没有被 g_signal_connect_after 连接的)
G_SIGNAL_RUN_CLEANUP 默认 handler 在所有用户定义 handler 和之后运行

参数

有时候, 对于信号的 handler 函数, 需要传递一些参数.

posted @ 2023-01-09 23:24  昤昽  阅读(43)  评论(0编辑  收藏  举报