Skip to content

Latest commit

 

History

History
482 lines (370 loc) · 21.4 KB

File metadata and controls

482 lines (370 loc) · 21.4 KB

Up: README.md, Prev: Section 10, Next: Section 12

Instance Initialization and Destruction

Encapsulation and Object

We've divided C source file into two parts.

  • tfetextview.h and tfetextview.c
  • tfeapplication.c

The TfeTextView object manages a GFile instance internally. Outside of this object, the only way to access the GFile is through the tfe_text_view_set_file and tfe_text_view_get_file functions. This demonstrates encapsulation.

By keeping variables hidden and controlling access through specific functions, this object-oriented design divides a program into manageable parts, making it easier to maintain.

In this section, we complete the TfeTextView program to show how the GObject system implements object-oriented encapsulation.

Class and Instance

The actual object created in memory is called an "instance." When an instance is created, the system allocates the necessary memory based on the definition of its object structure. For example, the object structure for TfeTextView is defined as follows:

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

When the instance tv is created using tv = tfe_text_view_new ();, memory for this structure is allocated internally, allowing it to manage its state. Multiple instances can exist simultaneously. Since each instance has its own memory area, it can hold its own unique state.

On the other hand, some functions used for managing the instances are stored as "function pointers" in a separate structure called a "class". Because these functions perform common operations for all instances, every instance shares a single class structure. In short, while multiple instances can be created, there is only one class in memory.

Inheritance

Objects have a mechanism called inheritance, which can be represented as a parent-child or ancestor-descendant relationship.

Note: Please be careful not to confuse this with the parent-child relationship of widgets, as they are completely different concepts.

Most objects are derived from GObject (although there are exceptions, such as GtkExpression, which is not derived from GObject). TfeTextView is also derived from GObject. Its inheritance hierarchy looks like this:

GObject - GInitiallyUnowned - GtkWidget - GtkTextView - TfeTextView

A child object inherits all the features of its parent object. As we will explain in detail in the later subsection, object inheritance is based on the inheritance of both the class structure and the object structure. For instance, the GObject class holds function pointers required for object destruction, and the TfeTextView class inherits these pointers. Furthermore, instead of simply inheriting them, a child class can rewrite these functions to suit its own needs. This is called "overriding."

We will explain the mechanism of inheritance in more detail in the next subsection, using TfeTextView as an example.

GObject Structure Inheritance

An instance is a block of memory allocated for the object structure. The following is the structure of TfeTextView.

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

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

The members of the structure are:

  • The member parent is a GtkTextView C structure. It is declared in gtktextview.h. GtkTextView is the parent object of TfeTextView. Note that this member is not a pointer, but the structure itself.
  • The member file is a pointer to a GFile. It can be NULL if the TfeTextView instance has no file associated with it. For example, it is initially NULL when the instance is created.

You can find the structural declarations of these ancestor objects in the GTK and GLib source files. The following snippets are extracted from the source files (simplified for clarity):

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;
};

In each structure, the parent instance is declared as the very first member. Because of this layout, the child object naturally includes all of its ancestors. The structure of a TfeTextView instance looks like the following diagram:

The structure of the instance TfeTextView

Derivable objects (ancestor objects) have their own private data areas, which are not directly included in the structures shown above. For example, GtkWidget uses the GtkWidgetPrivate C structure for its private data.

In C, a structure declaration merely defines a new type; it does not allocate any memory. Therefore, memory is only allocated from the heap when the tfe_text_view_new function is called. At the same time, the ancestors' private data areas are also allocated for the TfeTextView instance. These private areas are hidden from TfeTextView, meaning it cannot access them directly. This entire allocated memory block is called the instance. When a TfeTextView instance is created, it is allocated three distinct data areas:

  • The TfeTextView instance structure itself.
  • The GtkWidgetPrivate structure.
  • The GtkTextViewPrivate structure.

TfeTextView functions can only access its own instance structure. The GtkWidgetPrivate and GtkTextViewPrivate structures are managed exclusively by the functions of their respective ancestors. Consider the following example:

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

The parent function gtk_text_view_get_buffer accesses the GtkTextViewPrivate data associated with the tv instance. This private area contains a pointer to the GtkTextBuffer, which the function retrieves and returns. (The actual internal behavior is slightly more complicated, but this illustrates the concept).

This is how a TfeTextView instance effectively inherits the functions and data of its ancestors.

GObjectClass Structure Inheritance

Objects have class structure such as GObjectclass, GtkWidgetClass, GtkTextViewClass, and TfeTextViewClass. They are C structures, mainly including pointers to C functions. The functions are called class methods and used by the object itself or its descendant objects.

For example, GObject class is declared in gobject.h in GLib source files.

typedef struct _GObjectClass             GObjectClass;
typedef struct _GObjectClass             GInitiallyUnownedClass;

struct  _GObjectClass
{
  GTypeClass   g_type_class;

  /*< private >*/
  GSList      *construct_properties;

  /*< public >*/
  /* seldom overridden */
  GObject*   (*constructor)     (GType                  type,
                                 guint                  n_construct_properties,
                                 GObjectConstructParam *construct_properties);
  /* overridable methods */
  void       (*set_property)		(GObject        *object,
                                         guint           property_id,
                                         const GValue   *value,
                                         GParamSpec     *pspec);
  void       (*get_property)		(GObject        *object,
                                         guint           property_id,
                                         GValue         *value,
                                         GParamSpec     *pspec);
  void       (*dispose)			(GObject        *object);
  void       (*finalize)		(GObject        *object);
  /* seldom overridden */
  void       (*dispatch_properties_changed) (GObject      *object,
					     guint	   n_pspecs,
					     GParamSpec  **pspecs);
  /* signals */
  void	     (*notify)			(GObject	*object,
					 GParamSpec	*pspec);

  /* called when done constructing */
  void	     (*constructed)		(GObject	*object);

  /*< private >*/
  gsize		flags;

  gsize         n_construct_properties;

  gpointer pspecs;
  gsize n_pspecs;

  /* padding */
  gpointer	pdummy[3];
};

Most of the _GObjectClass structure members are pointers to functions. Two functions dispose and finalaize are called when the object is destructed. The details will be explained later.

Other ancestors' classes of the TfeTextView class are shown below. They are extracted from the GTK source codes, but not exactly the same.

struct _GtkWidgetClass
{
  GInitiallyUnownedClass parent_class;

  /*< public >*/

  /* basics */
  void (* show)                (GtkWidget        *widget);
  void (* hide)                (GtkWidget        *widget);
  void (* map)                 (GtkWidget        *widget);
  void (* unmap)               (GtkWidget        *widget);
  void (* realize)             (GtkWidget        *widget);
  void (* unrealize)           (GtkWidget        *widget);
  void (* root)                (GtkWidget        *widget);
  void (* unroot)              (GtkWidget        *widget);
  void (* size_allocate)       (GtkWidget           *widget,
                                int                  width,
                                int                  height,
                                int                  baseline);
  void (* state_flags_changed) (GtkWidget        *widget,
                                GtkStateFlags     previous_state_flags);
  void (* direction_changed)   (GtkWidget        *widget,
                                GtkTextDirection  previous_direction);

  /* size requests */
  GtkSizeRequestMode (* get_request_mode)               (GtkWidget      *widget);
  void              (* measure) (GtkWidget      *widget,
                                 GtkOrientation  orientation,
                                 int             for_size,
                                 int            *minimum,
                                 int            *natural,
                                 int            *minimum_baseline,
                                 int            *natural_baseline);

  /* Mnemonics */
  gboolean (* mnemonic_activate)        (GtkWidget           *widget,
                                         gboolean             group_cycling);

  /* explicit focus */
  gboolean (* grab_focus)               (GtkWidget           *widget);
  gboolean (* focus)                    (GtkWidget           *widget,
                                         GtkDirectionType     direction);
  void     (* set_focus_child)          (GtkWidget           *widget,
                                         GtkWidget           *child);

  /* keyboard navigation */
  void     (* move_focus)               (GtkWidget           *widget,
                                         GtkDirectionType     direction);
  gboolean (* keynav_failed)            (GtkWidget           *widget,
                                         GtkDirectionType     direction);

  gboolean     (* query_tooltip)      (GtkWidget  *widget,
                                       int         x,
                                       int         y,
                                       gboolean    keyboard_tooltip,
                                       GtkTooltip *tooltip);

  void         (* compute_expand)     (GtkWidget  *widget,
                                       gboolean   *hexpand_p,
                                       gboolean   *vexpand_p);

  void         (* css_changed)                 (GtkWidget            *widget,
                                                GtkCssStyleChange    *change);

  void         (* system_setting_changed)      (GtkWidget            *widget,
                                                GtkSystemSetting      settings);

  void         (* snapshot)                    (GtkWidget            *widget,
                                                GtkSnapshot          *snapshot);

  gboolean     (* contains)                    (GtkWidget *widget,
                                                double     x,
                                                double     y);

  /*< private >*/

  GtkWidgetClassPrivate *priv;

  gpointer padding[8];
};

struct _GtkTextViewClass
{
  GtkWidgetClass parent_class;

  /*< public >*/

  void (* move_cursor)           (GtkTextView      *text_view,
                                  GtkMovementStep   step,
                                  int               count,
                                  gboolean          extend_selection);
  void (* set_anchor)            (GtkTextView      *text_view);
  void (* insert_at_cursor)      (GtkTextView      *text_view,
                                  const char       *str);
  void (* delete_from_cursor)    (GtkTextView      *text_view,
                                  GtkDeleteType     type,
                                  int               count);
  void (* backspace)             (GtkTextView      *text_view);
  void (* cut_clipboard)         (GtkTextView      *text_view);
  void (* copy_clipboard)        (GtkTextView      *text_view);
  void (* paste_clipboard)       (GtkTextView      *text_view);
  void (* toggle_overwrite)      (GtkTextView      *text_view);
  GtkTextBuffer * (* create_buffer) (GtkTextView   *text_view);
  void (* snapshot_layer)        (GtkTextView      *text_view,
			          GtkTextViewLayer  layer,
			          GtkSnapshot      *snapshot);
  gboolean (* extend_selection)  (GtkTextView            *text_view,
                                  GtkTextExtendSelection  granularity,
                                  const GtkTextIter      *location,
                                  GtkTextIter            *start,
                                  GtkTextIter            *end);
  void (* insert_emoji)          (GtkTextView      *text_view);

  /*< private >*/

  gpointer padding[8];
};

/* The following definition is generated by the macro G_DECLARE_FINAL_TYPE */
typedef struct {
  GtkTextView parent_class;
} TfeTextViewClass;
  • 120-122: These three lines are generated by the macro G_DECLARE_FINAL_TYPE. So, they are not written in either tfetextview.h or tfetextview.c.
  • 3, 84, 121: Each class has its parent class at the first member of its structure. It is the same as instance structures.
  • Class members in ancestors are open to the descendant class. So, they can be changed in tfe_text_view_class_init function. For example, the dispose pointer in GObjectClass will be overridden later in tfe_text_view_class_init. (Override is an object oriented programming terminology. Override is rewriting ancestors' class methods in the descendant class.)
  • Some class methods are often overridden. set_property, get_property, dispose, finalize and constructed are such methods.

TfeTextViewClass includes its ancestors' class in it. It is illustrated in the following diagram.

The structure of TfeTextView Class

Instance Initialization Process

By convention, instances are created using a function named object_name_new (for example, tfe_text_view_new). However, because this function calls g_object_new internally, it is actually g_object_new that serves as the core function for creating instances in the system.

When this function is called, an initialization process takes place as follows (the actual behavior is more complex, but this is a simplified overview):

  • If the class has not been created yet, memory for the class structure is allocated and initialized.
  • Memory required for the instance is allocated and initialized.

Both class and instance initializations are performed in a top-down order, from parent to child.

Objects generally fall into two categories: "derivable types", which can have child objects, and "final types", which cannot. TfeTextView is a final type.

Since final type objects do not expose their class structures, you don't add new class members to them. In fact, you don't need to write the class structure definition yourself at all; a macro (G_DECLARE_FINAL_TYPE) handles it for you.

When using the G_DEFINE_FINAL_TYPE macro, the instance initialization function must be named object_name_init. For TfeTextView, this function is tfe_text_view_init.

static void
tfe_text_view_init (TfeTextView *tv) {
  /* The allocated memory is set to zero by the system.*/
  /* Therefore, "tv->file" is automatically initialized to NULL; setting it explicitly is unnecessary here. */
}

The pointer to a GFile must be initialized to be NULL.

Note: When the system allocates memory for an instance, it automatically clears all variables to 0 or NULL. Therefore, explicitly writing tv->file = NULL; is actually unnecessary. You only need to write code here if you are allocating dynamic memory or setting specific default values.

The system calls these initialization functions automatically. As long as you define the functions with the correct names according to the rules, the system will handle the rest.

Reference Count

Every Object derived from GObject has a reference count. If an object A refers to an object B, then A keeps a pointer to B in A and at the same time increases the reference count of B by one with the function g_object_ref (B). If A doesn't need B any longer, A discards the pointer to B (usually it is done by assigning NULL to the pointer) and decreases the reference count of B by one with the function g_object_unref (B).

If two objects A and B refer to C, then the reference count of C is two. If A no longer needs C, A discards the pointer to C and decreases the reference count in C by one. Now the reference count of C is one. In the same way, if B no longer needs C, B discards the pointer to C and decreases the reference count in C by one. At this moment, no object refers to C and the reference count of C is zero. This means C is no longer useful. Then C destructs itself and finally the memories allocated to C will be freed.

Reference count of B

The idea above is based on an assumption that an object referred by nothing has reference count of zero. When the reference count drops to zero, the object starts its destruction process.

Instance Destruction Process

The destruction process is split into two phases: disposing and finalizing. In the disposing process, the object calls the function pointed to by dispose in its class to release all references to other instances. After that, it calls the function pointed to by finalize in its class to free memory used by the instance and complete the destruction process.

At the beginning of the creation of the instance, these class member points to the dispose and finalize functions for GObject. You need to override these functions for TfeTextView in the class initialization.

  • Dispose functions release objects with g_clear_object(). The functions decrease the reference count by one and clear the pointer to the objects.
  • Finalize functions free memory with g_free() that has allocated in the initialization process.
static void
tfe_text_view_dispose (GObject *gobject) {
  TfeTextView *tv = TFE_TEXT_VIEW (gobject);

  g_clear_object (&tv->file);
  G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);
}

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

  object_class->dispose = tfe_text_view_dispose;
}

In the destruction process, TfeTextView needs to unref the GFile pointed to by tv->file and clear the pointer.

  • 5: The function g_clear_object do nothing if tv->file == NULL (not &tv->file == NULL). If tv->file points to a GFile, it decreases the reference count of the GFile and assigns NULL to tv->file. In dispose handlers, we usually use g_clear_object rather than g_object_unref.
  • 6: calls parent's dispose handler.
  • The dispose method needs to be overridden by tfe_text_view_dispose when the TfeTextView class is initialized. The function tfe_text_view_class_init first cast the variable class to point the GObjectclass. Because dispose is defined as a member of GObjectclass.

Each ancestors' class has been created before the TfeTextView class is created. Therefore, there are four classes and each class has a pointer to each dispose handler. Look at the following diagram. There are four classes -- GObjectClass (GInitiallyUnownedClass), GtkWidgetClass, GtkTextViewClass, and TfeTextViewClass. Each class has its own dispose handler -- dh1, dh2, dh3 and tfe_text_view_dispose.

dispose handlers

Now, look at the tfe_text_view_dispose program above. It first releases the reference to GFile object pointed to by tv->file. Then it calls its parent's dispose handler.

G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject);

A variable tfe_text_view_parent_class, which is made by G_DEFINE_FINAL_TYPE macro, is a pointer that points to the parent object class. The variable gobject is a pointer to TfeTextView instance which is cast as a GObject instance. Therefore, G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose points to the handler dh3 in the diagram above. The statement G_OBJECT_CLASS (tfe_text_view_parent_class)->dispose (gobject) is the same as dh3 (gobject), which means it releases all the reference to the other instances in the GtkTextViewPrivate in the TfeTextView instance. After that, dh3 calls dh2, and dh2 calls dh1. Finally all the references are released.

This process is called "chaining up".

Up: README.md, Prev: Section 10, Next: Section 12