十一、Gtk4-Instance Initialization and destruction

  文本文件编辑器(tfe)的新版本将在本节和以下四节中编写。它是tfe5。与之前的版本相比,有很多变化。它们位于两个目录中,src/tfe5和src/tfetextview。

1 封装

我们将C源文件分为两部分。但就封装而言,这还不够。

  • tfe.c包含除Tfe TextView之外的所有内容。它至少应该分为两部分,tfeapplication.c和tfenotebook.c。
  • 头文件也需要组织。

然而,首先,我想关注TfeTextView对象。它是GtkTextView的一个子对象,其中有一个新的成员文件。重要的是管理file指向的Gfile对象。

  • 当你创建(或初始化)TfeTextView,对于GFile什么是必须的?
  • 当你销毁TfeTextView,对于GFile什么是必须的?
  • TfeTextView是否应该自己读/写文件?
  • 它如何与其他的对象交流?

在考虑类、实例和信号之前,你至少需要知道它们。我将在本节和下一节中解释它们。之后我会解释:

  • 组织功能(Organizing functions)
  • 如何使用GtkFileChooserDialog

2 GObject and its children

GObject及其子对象都是对象,它们既有class C结构,也有object C结构。首先,考虑实例。一个实例是具有对象结构的内存。下面是TfeTextView的结构。

/* This typedef statement is automatically generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct _TfeTextView TfeTextView;

struct _TfeTextView {
  GtkTextView parent;
  GFile *file;
};

该结构的成员如下:

  • 父类的类型是GtkTextView,这是C结构体。它在gtktextview.h中声明。GtkTextView是TfeTextView的父类。
  • file是一个GFile类型的指针。如果没有文件对应于TfeTextView实例,它可以是NULL。

您可以在GTK和GLib的源文件中找到父类对象结构的声明。下面的代码是从源文件中提取的(不完全相同)。

typedef struct _GObject GObject;
typedef struct _GObject GInitiallyUnowned;
struct  _GObject
{
  GTypeInstance  g_type_instance;
  volatile guint ref_count;
  GData         *qdata;
};

typedef struct _GtkWidget GtkWidget;
struct _GtkWidget
{
  GInitiallyUnowned parent_instance;
  GtkWidgetPrivate *priv;
};

typedef struct _GtkTextView GtkTextView;
struct _GtkTextView
{
  GtkWidget parent_instance;
  GtkTextViewPrivate *priv;
};

在每个结构中,其父元素声明在成员元素的顶部。所以,所有的父类都包含在子对象中。TfeTextView的结构如下图所示。
在这里插入图片描述
派生类(父类)都有它们自己的私有数据区,这些私有数据区不包含在上述结构中。例如,GtkWidget的私有数据为GtkWidgetPrivate (C结构体)。

注意声明(declarations)不是定义(definitions)。因此,在声明C结构体时不会分配内存。在调用tfe_text_view_new函数时,会从堆区域为它们分配内存。同时,对于TfeTetView父类的私有区域也会被分配。它们从TfeTextView隐藏,不能直接访问它们。创建的内存称为实例(instance)。创建TfeTextView实例时,会给它三个数据区域。

  • 实例(C结构体)。
  • GtkWidgetPrivate结构体。
  • GtkTextViewPrivate结构体。

TfeTextView函数只能访问其实例。GtkWidgetPrivate和GtkTextViewPrivate被父类的函数使用。请看下面的例子。

GtkWidget *tv = tfe_text_view_new ();
GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));

父函数gtk_text_view_get_buffer访问GtkTextViewPrivate数据(由tv拥有)。私有区域中有一个指向GtkBuffer的指针,该函数返回该指针。(实际的行为有点复杂。)

TfeTextView实例像这样继承了父类函数。

每次调用tfe_text_view_new函数时,都会创建一个TfeTextView实例。因此,可以存在多个TfeTextView实例。

3 初始化TfeTextView实例

函数tfe_text_view_new创建了一个新的TfeTextView实例。

1 GtkWidget *
2 tfe_text_view_new (void) {
3   return GTK_WIDGET (g_object_new (TFE_TYPE_TEXT_VIEW, "wrap-mode", GTK_WRAP_WORD_CHAR, NULL));
4 }

调用这个函数时,会创建并初始化一个TfeTextView实例。初始化过程如下。

  • 当创建实例时,也创建了GtkWidgetPrivate和GtkTextViewPrivate结构体。
  • 在TfeTextView实例中初始化GObject (GInitiallyUnowned)部分。
  • 在TfeTextView实例和GtkWidgetPrivate结构中初始化GtkWidget部分(第一个priv)。
  • 初始化GtkTextView部分(第二个priv)在TfeTextView实例和GtkTextViewPrivate结构。
  • 在TfeTextView实例中初始化TfeTextView部分(文件)。

第二到第四步由g_object_init、gtk_widget_init和gtk_text_view_init完成。它们由系统自动调用,你不需要关心它们。第四步是由tfetextview.c中的函数tfe_text_view_init完成的。

1 static void
2 tfe_text_view_init (TfeTextView *tv) {
3   tv->file = NULL;
4 }

这个函数只是将tv->file初始化为NULL。

4 方法和类

在Gtk中,所有从GObject派生的对象都有类和实例(抽象对象除外)。实例是C结构体的内存,在前两个小节中描述过。每个对象可以有多个实例。这些实例具有相同的结构。实例只有数据。因此,它没有定义对象的行为。我们至少需要两样东西。一个是函数,另一个是类方法。

你已经见过很多函数了。例如,

  • TfeTextView *tfe_text_view_new (void);是一个创建TfeTextView实例的函数。
  • GtkTextBuffer *gtk_text_view_get_buffer (GtkTextView *textview)是一个从GtkTextView获取GtkTextBuffer的函数。

函数是公有的,这意味着它们可以被其他对象使用。它们类似于面向对象语言中的公共方法。

类(C结构体)主要由指向函数指针组成。这些函数被称为类方法,由对象本身或其后代对象使用。例如,GObject类在GLib源文件的GObject .h中声明。

 1 typedef struct _GObjectClass             GObjectClass;
 2 typedef struct _GObjectClass             GInitiallyUnownedClass;
 3 
 4 struct  _GObjectClass
 5 {
 6   GTypeClass   g_type_class;
 7 
 8   /*< private >*/
 9   GSList      *construct_properties;
10 
11   /*< public >*/
12   /* seldom overridden */
13   GObject*   (*constructor)     (GType                  type,
14                                  guint                  n_construct_properties,
15                                  GObjectConstructParam *construct_properties);
16   /* overridable methods */
17   void       (*set_property)		(GObject        *object,
18                                          guint           property_id,
19                                          const GValue   *value,
20                                          GParamSpec     *pspec);
21   void       (*get_property)		(GObject        *object,
22                                          guint           property_id,
23                                          GValue         *value,
24                                          GParamSpec     *pspec);
25   void       (*dispose)			(GObject        *object);
26   void       (*finalize)		(GObject        *object);
27   /* seldom overridden */
28   void       (*dispatch_properties_changed) (GObject      *object,
29 					     guint	   n_pspecs,
30 					     GParamSpec  **pspecs);
31   /* signals */
32   void	     (*notify)			(GObject	*object,
33 					 GParamSpec	*pspec);
34 
35   /* called when done constructing */
36   void	     (*constructed)		(GObject	*object);
37 
38   /*< private >*/
39   gsize		flags;
40 
41   gsize         n_construct_properties;
42 
43   gpointer pspecs;
44   gsize n_pspecs;
45 
46   /* padding */
47   gpointer	pdummy[3];
48 };
49 

第23行有一个指向函数dispose的指针。

void (*dispose) (GObject *object);

这个声明有点复杂。标识符dispose之前的星号表示指针。因此,指针分配指向一个只有一个参数的函数,它指向一个GObject结构,并且没有返回值。同样,第24行说finalize是一个指向函数的指针,它有一个参数,指向GObject结构,并且没有返回值。

void (*finalize) (GObject *object);

看看_GObjectClass的声明,你会发现大多数成员都是指向函数的指针。

  • 13:当实例创建的时候,通过constructor调用被指向的函数。它完成实例的初始化。
  • 25:当实例析构时调用dispose指向的函数。销毁过程分为两个阶段。第一个被称为处理(disposing)。在这个阶段,实例释放对其他实例的所有引用。第二阶段是finalizing。
  • 26:由finalize指向的函数完成析构过程。
  • 其他指针指向在实例生命周期期间调用的函数。

这些函数称为类方法(class method)。这些方法对其后代开放。但不向非后代对象开放。

5 TfeTextView类

TfeTextView类是一个结构体,它包含了它所有父类。因此,类与实例具有相似的层次结构。

GObjectClass (GInitiallyUnownedClass) -- GtkWidgetClass -- GtkTextViewClass -- TfeTextViewClass

下面的代码从源代码中提取(不完全相同)。

  1 struct _GtkWidgetClass
  2 {
  3   GInitiallyUnownedClass parent_class;
  4 
  5   /*< public >*/
  6 
  7   /* basics */
  8   void (* show)                (GtkWidget        *widget);
  9   void (* hide)                (GtkWidget        *widget);
 10   void (* map)                 (GtkWidget        *widget);
 11   void (* unmap)               (GtkWidget        *widget);
 12   void (* realize)             (GtkWidget        *widget);
 13   void (* unrealize)           (GtkWidget        *widget);
 14   void (* root)                (GtkWidget        *widget);
 15   void (* unroot)              (GtkWidget        *widget);
 16   void (* size_allocate)       (GtkWidget           *widget,
 17                                 int                  width,
 18                                 int                  height,
 19                                 int                  baseline);
 20   void (* state_flags_changed) (GtkWidget        *widget,
 21                                 GtkStateFlags     previous_state_flags);
 22   void (* direction_changed)   (GtkWidget        *widget,
 23                                 GtkTextDirection  previous_direction);
 24 
 25   /* size requests */
 26   GtkSizeRequestMode (* get_request_mode)               (GtkWidget      *widget);
 27   void              (* measure) (GtkWidget      *widget,
 28                                  GtkOrientation  orientation,
 29                                  int             for_size,
 30                                  int            *minimum,
 31                                  int            *natural,
 32                                  int            *minimum_baseline,
 33                                  int            *natural_baseline);
 34 
 35   /* Mnemonics */
 36   gboolean (* mnemonic_activate)        (GtkWidget           *widget,
 37                                          gboolean             group_cycling);
 38 
 39   /* explicit focus */
 40   gboolean (* grab_focus)               (GtkWidget           *widget);
 41   gboolean (* focus)                    (GtkWidget           *widget,
 42                                          GtkDirectionType     direction);
 43   void     (* set_focus_child)          (GtkWidget           *widget,
 44                                          GtkWidget           *child);
 45 
 46   /* keyboard navigation */
 47   void     (* move_focus)               (GtkWidget           *widget,
 48                                          GtkDirectionType     direction);
 49   gboolean (* keynav_failed)            (GtkWidget           *widget,
 50                                          GtkDirectionType     direction);
 51 
 52   gboolean     (* query_tooltip)      (GtkWidget  *widget,
 53                                        int         x,
 54                                        int         y,
 55                                        gboolean    keyboard_tooltip,
 56                                        GtkTooltip *tooltip);
 57 
 58   void         (* compute_expand)     (GtkWidget  *widget,
 59                                        gboolean   *hexpand_p,
 60                                        gboolean   *vexpand_p);
 61 
 62   void         (* css_changed)                 (GtkWidget            *widget,
 63                                                 GtkCssStyleChange    *change);
 64 
 65   void         (* system_setting_changed)      (GtkWidget            *widget,
 66                                                 GtkSystemSetting      settings);
 67 
 68   void         (* snapshot)                    (GtkWidget            *widget,
 69                                                 GtkSnapshot          *snapshot);
 70 
 71   gboolean     (* contains)                    (GtkWidget *widget,
 72                                                 double     x,
 73                                                 double     y);
 74 
 75   /*< private >*/
 76 
 77   GtkWidgetClassPrivate *priv;
 78 
 79   gpointer padding[8];
 80 };
 81 
 82 struct _GtkTextViewClass
 83 {
 84   GtkWidgetClass parent_class;
 85 
 86   /*< public >*/
 87 
 88   void (* move_cursor)           (GtkTextView      *text_view,
 89                                   GtkMovementStep   step,
 90                                   int               count,
 91                                   gboolean          extend_selection);
 92   void (* set_anchor)            (GtkTextView      *text_view);
 93   void (* insert_at_cursor)      (GtkTextView      *text_view,
 94                                   const char       *str);
 95   void (* delete_from_cursor)    (GtkTextView      *text_view,
 96                                   GtkDeleteType     type,
 97                                   int               count);
 98   void (* backspace)             (GtkTextView      *text_view);
 99   void (* cut_clipboard)         (GtkTextView      *text_view);
100   void (* copy_clipboard)        (GtkTextView      *text_view);
101   void (* paste_clipboard)       (GtkTextView      *text_view);
102   void (* toggle_overwrite)      (GtkTextView      *text_view);
103   GtkTextBuffer * (* create_buffer) (GtkTextView   *text_view);
104   void (* snapshot_layer)        (GtkTextView      *text_view,
105 			          GtkTextViewLayer  layer,
106 			          GtkSnapshot      *snapshot);
107   gboolean (* extend_selection)  (GtkTextView            *text_view,
108                                   GtkTextExtendSelection  granularity,
109                                   const GtkTextIter      *location,
110                                   GtkTextIter            *start,
111                                   GtkTextIter            *end);
112   void (* insert_emoji)          (GtkTextView      *text_view);
113 
114   /*< private >*/
115 
116   gpointer padding[8];
117 };
118 
119 /* The following definition is generated by the macro G_DECLARE_FINAL_TYPE */
120 typedef struct {
121   GtkTextView parent_class;
122 } TfeTextViewClass;
123 
  • 120-122:这3行代码由宏G_DECLARE_FINAL_TYPE生成。因此,它们不是写在tfe_text_view.h或tfe_text_view.c中。
  • 3,84, 121:每个类在其结构的第一个成员处都有父类。它与实例结构相同。
  • 父类中的类成员对后代类开放。因此,它们可以在tfe_text_view_class_init函数中更改。例如,GObjectClass中的finalize指针将在稍后的tfe_text_view_class_init中被覆盖。Override是一个面向对象的编程术语。Override是在子类中重写父类的方法。)
  • 有些类方法经常会被重写。set_property、get_property、dispose、finalize和construct就是这样的方法。
    在这里插入图片描述

6 TfeTextView销毁

从GObject派生的每个对象都有一个引用计数。如果一个对象A引用了一个对象B,那么A会在A中保留一个指向B的指针,同时使用g_object_ref (B)函数将B的引用计数增加1。如果A不再需要B,那么A会丢弃指向B的指针(通常是通过将指针赋值NULL来实现,表示不再使用),并使用g_object_unref (B)函数将B的引用计数减少1。

如果两个对象A和B都指向C,那么C的引用计数是2。如果A不再需要C,则A丢弃指向C的指针,并将C中的引用计数减1。现在C的引用计数是1。同样,如果B不再需要C,则B丢弃指向C的指针,并将C中的引用计数减少1。此时,没有对象引用C, C的引用计数为0。这意味着C不再有用。然后,C会销毁自身,最终分配给C的内存会被释放。

在这里插入图片描述
上面的想法是基于一个假设,即由nothing引用的对象的引用计数为零。当引用计数降到0时,对象开始销毁过程。销毁过程分为两个阶段:disposing和finalizing。在disposing过程中,对象调用其类中的dispose指向的函数,以释放对其他实例的所有引用。之后,它会调用类中的finalize指向的函数来完成析构过程。例如,处理程序或处理方法。

在销毁过程中,TfeTextView需要解引用通过tv->file指向的GFile。必须编写处理程序tfe_text_view_dispose。

1 static void
2 tfe_text_view_dispose (GObject *gobject) {
3   TfeTextView *tv = TFE_TEXT_VIEW (gobject);
4 
5   if (G_IS_FILE (tv->file))
6     g_clear_object (&tv->file);
7 
8   G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
9 }
  • 5、6:如果tv->file指向GFile,则减少其引用计数。g_clear_object减少引用计数并将NULL赋值给tv->file。在处置处理程序中,我们通常使用g_clear_object而不是g_object_unref。
  • 8:调用父类的dispose处理程序。(稍后会解释。)

在处理过程中,对象使用其类中的指针来调用处理程序。因此,在初始化TfeTextView类时,需要在类中注册tfe_text_view_dispose。函数tfe_text_view_class_init是类初始化函数,它在G_DEFINE_TYPE宏扩展中声明。

static void
tfe_text_view_class_init (TfeTextViewClass *class) {
  GObjectClass *object_class = G_OBJECT_CLASS (class);

  object_class->dispose = tfe_text_view_dispose;

}

每个父类在创建TfeTextViewClass之前已经创建。因此,有四个类,每个类都有一个指向每个释放处理程序的指针。请看下图。有四个类——GObjectClass (GInitiallyUnownedClass), GtkWidgetClass, GtkTextViewClass和TfeTextViewClass。每个类都有自己的处理程序——dh1、dh2、dh3和tfe_text_view_dispose。
在这里插入图片描述现在,看看上面的tfe_text_view_dispose程序。它首先释放tv->file指向的GFile对象的引用。然后在第8行中调用其父类的dispose处理程序。

G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);

变量tfe_text_view_parent_class由G_DEFINE_TYPE宏创建,是一个指向父对象类的指针。gobject变量是一个指向TfeTextView实例的指针,该实例被强制转换为gobject实例。因此,G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose指向上图中的处理程序dh3。语句G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject)与dh3 (gobject)相同,这意味着它在TfeTextView实例中,会释放GtkTextViewPrivate中其他实例的所有引用。之后,dh3调用dh2, dh2调用dh1。最后释放所有的引用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值