首页 > 其他分享 >GObject Note

GObject Note

时间:2023-01-09 23:36:32浏览次数:34  
标签:INTEGER signal value Note integer GObject TInteger TYPE

初创建于: 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 函数, 需要传递一些参数.

标签:INTEGER,signal,value,Note,integer,GObject,TInteger,TYPE
From: https://www.cnblogs.com/Corona09/p/17038865.html

相关文章

  • VimScript Note
    初创建于:2022-02-1621:19VimScript要想写vim插件,学习vimscript还真是不能省的啊.暂定vim插件的学习路线为:vimscript\(\rightarrow\)lua\(\rightarrow\)types......
  • note_2023年1月9日22点46分
    D:\code_gitee\python_socket\agvServer.tsimport{createServer}from"net";constserver=createServer();server.listen(19204,"localhost");server.on("conn......
  • 01notepad++如何比较两个文件内容
    下面介绍,通过在NotePad中下载安装Compare插件,可以比较两个文件内容。(推荐学习:notepad++)打开NotePad,点击工具栏上的【插件】–》PluginManager—》ShowPluginManage......
  • 解决jupyter-notebook启动缺少模块No module named 'ipyparallel'
    1、启动jupyter-notebook启动缺少模块Nomodulenamed'ipyparallel',具体:Errorloadingserverextensionipyparallel.nbextensionTraceback(mostrecentcalll......
  • Jupyter notebook如何打印多个结果
    Jupyternotebook如何打印多个结果fromIPython.core.interactiveshellimportInteractiveShellInteractiveShell.ast_node_interactivity='all'InteractiveShell.ast......
  • GoodNotes 5 for Mac(笔记软件) 5.8.13中文版
    GoodNotes是一款实用的笔记记录软件,goodnotes不仅仅能够识别pdf文件和图片,而且还支持手写编辑文件功能,goodnotes电脑版能够将手写内容转换为文本,软件能够让用户在导入的PDF......
  • Jupyter notebook
     JupyterNotebook工作空间/默认路径的设置方式​​ Jupyternotebook查看Markdown.md文件​​JupyterNotebook介绍、安装及使用教程 https://www.jianshu.com/p/......
  • 用hardhat 做ERC20转帐时报了 Nonce too high. Expected nonce to be 6 but got 11. N
    https://github.com/scaffold-eth/scaffold-eth/issues/476InMetamask,ensureyouareonyourdev/testaccountthen:clickontheavatarcircletoprightInthe......
  • Notepad++下载避坑
    是免费的编辑器NOTEPAD++,今天下载时竟然发现了假冒网站,安装还要收费。新手千万不要上当,真是良心叫狗吃了。官网地址:https://notepad-plus.en.softonic.com/ 第一个程序......
  • Jupyter Notebook入门指南
    作者:京东科技隐私计算产品部孙晓军1.JupyterNotebook介绍图1Jupter项目整体架构[​​https://docs.jupyter.org/en/latest/projects/architecture/content-architecture......