GObject 简明教程(一)


前言

最近,我一直在深入学习 GStreamer(相关教程可以从我主页中看到)。在探索其复杂的设计逻辑时,我意识到需要深入研究 GStreamer 的源码。而因为 GStreamer 是基于 GObject 开发的,掌握 GObject 的基本概念就显得尤为重要。

本文整理自 GObject Tutorial for beginners,简化了细节,保留了一些关键内容,以便更好地理解。


一、准备工作

  • 本文涉及到的代码可以在 Gobject-tutorial
  • 代码中依赖了 glib,你需要自行安装,在 macos 上可以使用 brew install glib 进行安装。其他平台应该也类似吧
  • 代码使用了 meson 进行编译,需要自行安装 meson;或者使用 CLion 打开 meson.build 文件,自动导入工程

二、GObject

摘要:本章介绍 GObject 中的基本概念,包括 class、instance 、引用计数等

2.1 Class 与 Instance

类与实例的概念,在 C++ 中我们比较少讨论这两者详细的定义与区别,但在 GObject 需要对它们有深刻的认知。GObject 中,Class 就像是一个"图纸",定义了对象的属性和行为;而 Instance 则是根据这个图纸"建造"出来的具体实例。比如,"汽车设计图"是 Class,而根据图纸造出的具体汽车就是 Instance。每个 Instance 都遵循 Class 的定义,但有自己的状态(如不同的颜色、里程数)。

  • 一个 GObject class 在调用第一次 g_object_new 时被创建,接下去无论创建多少个 instance,class 只会被创建一次
  • 一个 GObject instance 在调用 g_object_new 时创建,可以创建多个实例
  • 运行 example1.c 可以看到,多个实例对应一个 class
$ cd src/misc; _build/example1
The address of instance1 is 0x55895eaf7ad0
The address of instance2 is 0x55895eaf7af0
The address of the class of instance1 is 0x55895eaf7880
The address of the class of instance2 is 0x55895eaf7880

在这里插入图片描述

2.2 引用计数

类似于 std::shared_ptr,不过需要手动的调用 g_object_refg_object_unref 来增减引用数量,当引用为 0 时,释放实例。

int
main (int argc, char **argv) {
  GObject *instance;
  
  instance = g_object_new (G_TYPE_OBJECT, NULL);
  g_print ("Call g_object_new.\n");
  show_ref_count (instance);
  g_object_ref (instance);
  g_print ("Call g_object_ref.\n");
  show_ref_count (instance);
  g_object_unref (instance);
  g_print ("Call g_object_unref.\n");
  show_ref_count (instance);
  g_object_unref (instance);
  g_print ("Call g_object_unref.\n");
  g_print ("Now the reference count is zero and the instance is destroyed.\n");
  g_print ("The instance memories are possibly returned to the system.\n");
  g_print ("Therefore, the access to the same address may cause a segmentation error.\n");
  
  return 0;
}
$ cd src/misc; _build/example2
bash: cd: src/misc: No such file or directory
Call g_object_new.
Reference count is 1.
Call g_object_ref.
Reference count is 2.
Call g_object_unref.
Reference count is 1.
Call g_object_unref.
Now the reference count is zero and the instance is destroyed.
The instance memories are possibly returned to the system.
Therefore, the access to the same address may cause a segmentation error.

2.3 GObject 初始化与释放过程。

  • 初始化过程
    1. 将 GObject 注册到 type system 中。这个过程在 main 函数被调用前被执行。
    2. 申请 GObjectClass 内存 和 GObject 内存(类似调用 malloc 申请内存)
    3. 初始化 GObjectClass 内存(类似 C++ 中的构造函数)
    4. 初始化 GObject 内存(类似 C++ 中构造函数)
  • 释放过程
    1. 释放资源(调用 finalize 方法)
    2. 释放自身内存(类似调用 free 方法)
    3. (可选)释放 class 的资源与内存。如果 GObject 是 static type,那么这份资源不会被释放。

三、Type system and registration process

摘要:本章通过定义一个 TDouble 类来说明 GObject 中 Type System 和 Type 的注册过程

  • GObject 命名约定规则:对象名 = 命名空间 + 名称
    • 示例:
      • GObject = G + Object
      • GtkWidget = Gtk + Widget
      • TDouble = T + Double
  • 定义 TDoubleClass ,如下。注意第一个成员变量必须是父类的类结构
typedef struct _TDoubleClass TDoubleClass;
struct _TDoubleClass {
  GObjectClass parent_class;
};
  • 定义 TDouble,如下。注意第一个成员变量必须是父类实例的结构
typedef struct _TDouble TDouble;
struct _TDouble {
  GObject parent;
  double value;
};
  • 创建 GObject 子类的过程

    1. 将 TDouble 注册到 Type System 中
    2. Type System 负责申请 TDoubleClass 和 TDouble 的内存
    3. 初始化 TDoubleClass
    4. 初始化 TDouble 实例
  • 注册类型到 Type System。关键函数g_type_register_static ,定义如下。参数说明:

    1. parent_type:父类型。
    2. type_name:类型名称。例如,“TDouble”。
    3. info:类型的信息。将会在下文中解释GTypeInfo结构。
    4. flags:标志。如果类型是抽象类型或抽象值类型,则设置其标志。否则,将其设置为零。
GType
g_type_register_static (GType           parent_type,
                        const gchar     *type_name,
                        const GTypeInfo *info,
                        GTypeFlags      flags);
  • GTypeInfo 定义了很多重要函数,例如:
    1. class_init: 初始化类的静态成员。将你的类初始化函数分配给class_init成员。按照惯例,名称为<命名空间>_<名称>_class_init,例如,t_double_class_init。
    2. instance_init: 初始化实例成员。将你的实例初始化函数分配给instance_init成员。按照惯例,名称为<命名空间>_<名称>_init,例如,t_double_init。
  • t_double_get_type 返回一个 GType,通过 GType 使用 g_object_new 申请实例
int main (int argc, char **argv) {
  GType dtype;
  TDouble *d;

  dtype = t_double_get_type (); /* or dtype = T_TYPE_DOUBLE */
  if (dtype)
    g_print ("Registration was a success. The type is %lx.\n", dtype);
  else
    g_print ("Registration failed.\n");

  d = g_object_new (T_TYPE_DOUBLE, NULL);
  if (d)
    g_print ("Instantiation was a success. The instance address is %p.\n", d);
  else
    g_print ("Instantiation failed.\n");
  g_object_unref (d); /* Releases the object d. */

  return 0;
}
  • G_DEFINE_TYPE 是 GObject 系统中一个非常重要的宏,用于简化类型定义和注册过程。使用 G_DEFINE_TYPE 可以自动生成 type_name_register_type 函数的定义(示例中就是 t_double_get_type
  • G_DECLARE_FINAL_TYPE 是 GObject 系统中用于声明最终(不可继承)类型的宏。它帮助我们自动生成一些重要函数的声明(你可以对比下原文中使用宏前后的区别)
  • 头文件与实现分离。在 .h 中使用 G_DECLARE_FINAL_TYPE 帮助我们声明重要函数;在 .c 中使用 G_DEFINE_TYPE 自动生成重要函数的实现。

四、Signals

摘要:介绍 GObject 中的信号系统

  • 类之间可以通过信号进行交流,比如完成了某件事情,类可以发送一个信号
  • GObject 中使用信号的流程:
    1. 在 class 初始化中注册信号
    2. 写一个 handler 来处理信号(通常是一个回调函数)
    3. 连接信号和 handler。如此,当信号发出时,handler 可以进行处理
    4. 发送信号
  • 注册信号,重要函数 g_signal_new,使用示例如下。关键参数说明
    1. 信号 ID:由 g_signal_new 返回
    2. 对象类型:使用 G_TYPE_FROM_CLASS(class) 获取
    3. 信号标志:定义信号的行为特征
    4. 返回类型:G_TYPE_NONE 表示无返回值
    5. 参数数量:本例中为 0
t_double_signal =
g_signal_new ("div-by-zero",
              G_TYPE_FROM_CLASS (class),
              G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
              0 /* class offset.Subclass cannot override the class handler (default handler). */,
              NULL /* accumulator */,
              NULL /* accumulator data */,
              NULL /* C marshaller. g_cclosure_marshal_generic() will be used */,
              G_TYPE_NONE /* return_type */,
              0     /* n_params */
              );
  • 定义的 handler 最后一个参数默认为 user_data,如果是定义的 handler 包含多个参数,示例如下:
signals[SIGNAL_DATA_CHANGED] = g_signal_new ("data-changed",
                                               G_TYPE_FROM_CLASS (klass),
                                               G_SIGNAL_RUN_LAST,
                                               0,  // class offset
                                               NULL,  // accumulator
                                               NULL,  // accumulator data
                                               g_cclosure_marshal_generic,
                                               G_TYPE_NONE,  // return type
                                               3,           // 参数数量
                                               G_TYPE_INT,   // 第一个参数类型
                                               G_TYPE_STRING, // 第二个参数类型
                                               G_TYPE_DOUBLE  // 第三个参数类型
                                               );
// 信号处理函数示例
static void
on_data_changed (MyWidget *widget,
                gint value,
                const gchar *message,
                gdouble number,
                gpointer user_data)
{
   g_print ("Data changed: value=%d, message=%s, number=%f\n",
            value, message, number);
}
  • 通过 g_signal_connect 连接信号和 handler,例如
g_signal_connect (self, "div-by-zero", G_CALLBACK (div_by_zero_cb), user_data);
  • 通过 g_signal_emit 发送信息,例如:
g_signal_emit (self, t_double_signal, 0);
  • g_signal_new_class_handler 定义默认的 handler;当使用 g_signal_connect 时,会覆盖默认的 handler;使用 g_signal_connect_after 时,先调用默认 handler,然后调用你定义的 handler

五、Properties

摘要:介绍 GObject 中的属性,如何获取属性、设置属性等

  • 设置属性的函数包括 g_object_newg_object_set;获取属性函数 g_object_get。示例如下:
GtkWindow *win;
win = g_object_new (GTK_TYPE_WINDOW, "title", "Hello", "default-width", 800, "default-height", 600, NULL);
GtkWindow *win;
win = g_object_new (GTK_TYPE_WINDOW, NULL);
g_object_set (win, "title", "Good bye", NULL);
GtkWindow *win;
char *title;
int width, height;

win = g_object_new (GTK_TYPE_WINDOW, "title", "Hello", "default-width", 800, "default-height", 600, NULL);
g_object_get (win, "title", &title, "default-width", &width, "default-height", &height, NULL);
g_print ("%s, %d, %d\n", title, width, height);
g_free (title);
  • GParamSpec 是 GObject 类型系统中的一个结构体,用于描述对象属性的元数据。GParamSpec 提供了一种机制,帮助开发者定义属性的类型、范围、默认值等信息,从而使属性能够以一种通用和一致的方式进行操作和管理。示例:
double_property = g_param_spec_double ("value", "val", "Double value",
                                       -G_MAXDOUBLE, G_MAXDOUBLE, 0.0,
                                        G_PARAM_READWRITE);
  • g_object_class_install_property 是在 GObject 类型系统中用于向对象类注册属性的函数。它将属性的元数据与对象类关联起来,使得该属性可以在对象的实例中使用。示例
static void
t_double_class_init (TDoubleClass *class) {
  GObjectClass *gobject_class = G_OBJECT_CLASS (class);

  gobject_class->set_property = t_double_set_property;
  gobject_class->get_property = t_double_get_property;
  double_property = g_param_spec_double ("value", "val", "Double value", -G_MAXDOUBLE, G_MAXDOUBLE, 0.0, G_PARAM_READWRITE);
  g_object_class_install_property (gobject_class, PROP_DOUBLE, double_property);
}
  • GValue 是 GObject 系统中用于表示任意类型值的通用容器,概念和基本操作如下:
typedef struct {
    GType g_type;          // 值的类型
    union {                // 实际存储的值
        gint v_int;
        guint v_uint;
        glong v_long;
        gulong v_ulong;
        gint64 v_int64;
        guint64 v_uint64;
        gfloat v_float;
        gdouble v_double;
        gpointer v_pointer;
        // ... 其他类型
    } data[2];
} GValue;

// 初始化 GValue
GValue value = G_VALUE_INIT;  // 初始化为空值
g_value_init (&value, G_TYPE_INT);  // 初始化为特定类型

// 设置值
g_value_set_int (&value, 42);
g_value_set_string (&value, "hello");
g_value_set_double (&value, 3.14);

// 获取值
gint int_val = g_value_get_int (&value);
const gchar *str_val = g_value_get_string (&value);
gdouble double_val = g_value_get_double (&value);

// 清理
g_value_unset (&value);
  • GObject在一个属性被设置时会发出“notify”信号。当你将“notify”信号连接到你的处理程序时,可以指定一个细节,这个细节是属性的名称。细节通过“::”作为分隔符添加到信号名称中。例如:
g_signal_connect(G_OBJECT(d1), "notify::value", G_CALLBACK(notify_cb), NULL);
  • 为了在设置属性时发送信息,你可以
    1. 使用 g_object_set
    2. 或者使用 g_object_notify_by_pspec 主动发送信号
// 方式1:使用 g_object_set
void t_double_set_value (TDouble *d, double value) {
    g_return_if_fail (T_IS_DOUBLE (d));
    g_object_set (d, "value", value, NULL);
}

// 方式2:显式发出通知
void t_double_set_value (TDouble *self, double value) {
    g_return_if_fail (T_IS_DOUBLE (self));
    
    if (self->value != value) {
        self->value = value;
        g_object_notify_by_pspec (G_OBJECT (self),
                                obj_properties[PROP_VALUE]);
    }
}

参考

<think>好的,我需要帮助用户查找关于GLib编程的完整教程和指南。首先,用户提到他们已经参考了三个引用资源,其中引用[1]是关于GObject简明教程,引用[2]涉及GLib库的线程相关函数,引用[3]提到GTK+的使用和绑定。用户的需求是寻找GLib的完整教程,所以应该结合这些已有的资源,并补充其他可靠资料。 首先,我需要确认GLib的基础内容。GLib是GTK+的基础库,提供数据结构、线程、IO等实用功能。用户提供的引用[2]中提到了线程相关的函数,如g_pointer_bit_lock和g_get_num_processors,这可能是个入门资料的部分内容,但用户需要更完整的指南。 接下来,我应该考虑用户可能需要的教程结构。通常,完整的GLib教程会包括数据结构(如字符串处理、链表、哈希表)、文件操作、线程管理、事件循环等。需要确保覆盖这些方面,并参考现有的引用内容。例如,引用[1]中的GObject教程可能涉及GLib的对象系统,但用户明确询问的是GLib本身,可能需要更基础的内容。 然后,检查是否有官方文档推荐。GLib的官方文档是最权威的资源,应该优先推荐。同时,引用[3]提到GTK+被多种语言绑定,这可能意味着用户对多语言支持有兴趣,可以提及GLib在C以外的使用,但主要焦点还是在C语言上。 另外,用户提供的引用[2]中的线程部分可以作为教程部分,比如在讲解多线程编程时引用这些函数。需要确保示例代码正确,并符合用户给出的格式要求,如使用$...$和$$...$$来包裹数学表达式,但在此问题中可能较少涉及数学内容,更多是代码示例。 还需要考虑用户可能的实际应用场景,比如开发图形界面应用,结合GTK+(如引用[3]所述),所以可以推荐整合GLib和GTK+的教程。同时,提供学习路径,从基础到高级,包括数据结构、IO操作、线程管理等模块。 可能需要补充些外部资源链接,但根据用户的要求,应引用站内已有的引用内容。因此,需要将现有的引用内容整合到回答中,指出它们对应的部分,比如引用[2]对应线程章节,引用[1]可能涉及更底层的GObject知识,而引用[3]关联到GTK+的应用场景。 最后,生成相关问题,帮助用户深入理解GLib的不同方面,如数据结构、线程安全、与GObject的关系、在GTK+中的应用等。</think>以下是关于GLib编程的完整学习指南,结合基础概念、核心模块和实践资源: --- ### **GLib编程核心内容** 1. **基础数据结构** GLib提供高效的数据结构实现,包括动态字符串($GString$)、链表($GList$)、哈希表($GHashTable$)等。例如: ```c GHashTable *hash = g_hash_table_new(g_str_hash, g_str_equal); g_hash_table_insert(hash, "key", "value"); ``` 2. **线程与同步** 通过$GThread$和$GMutex$实现多线程管理,引用[2]中提到的`g_pointer_bit_lock`和`g_get_num_processors`属于线程工具函数。示例: ```c GMutex mutex; g_mutex_init(&mutex); g_mutex_lock(&mutex); // 临界区操作 g_mutex_unlock(&mutex); ``` 3. **文件与IO操作** 使用$GIOChannel$处理文件读写,支持异步操作: ```c GIOChannel *channel = g_io_channel_new_file("test.txt", "r", NULL); ``` 4. **事件循环(Main Loop)** GLib的主事件循环($GMainLoop$)是GUI应用的核心,与GTK+深度集成[^3]。 --- ### **推荐学习资源** 1. **官方文档** - [GLib Reference Manual](https://docs.gtk.org/glib/):最权威的API参考和概念解释。 2. **入门教程** - 引用[1]中的GObject教程可作为补充,理解GLib对象系统的基础[^1]。 - 引用[2]的线程章节适合多线程编程实践[^2]。 3. **书籍与综合指南** - *《The GLib/GTK+ Development Platform》*:系统讲解GLib与GTK+的整合开发。 4. **实战项目** - 结合引用[3],通过GTK+开发图形界面应用,例如实现个多线程文件管理器。 --- ### **学习路径建议** 1. 从基础数据类型(如$GArray$, $GSList$)开始练习。 2. 掌握内存管理($g_malloc$, $g_free$)和错误处理($GError$)。 3. 进阶学习事件循环和异步IO。 4. 结合GTK+开发完整应用,理解GLib在实际项目中的作用。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值