GLib
  • Initial page
  • I. Концепция
    • Background
    • Типы данных и программирование
  • Динамическая система типов Glib
    • Введение
    • Функции копирования
    • Соглашения
    • Неинстанциируемые не классифицированные базовые типы
    • Инстанциируемые классифицируемые типы: объекты
    • Неинстанциированные классифицированные типы: Интерфейсы.
  • Основной класс GObject
    • Введение
    • Инстанциация объекта
    • Объектное управление памятью
    • Свойства объекта
  • Система сообщений GObject
    • Замыкания
    • Сигналы
  • II. Описание API
    • GType
    • GTypePlugin
    • GTypeModule
    • GObject часть 1
    • GObject часть 2
    • Enums and Flags
    • GBoxed
    • Generic Values
    • Parameters and Values часть 1
    • Parameters and Values часть 2
    • Parameters and Values часть 3, ага
    • Value arrays
  • III. Описание инструментов
    • glib-mkenums
    • glib-genmarshal
    • gobject-query
  • IV. Руководство
    • Как определить и реализовать новый GObject?
    • Объектные методы
    • Как определять и реализовывать Интерфейсы?
    • Как создавать и использовать сигналы
    • Как пользователи могут злоупотреблять сигналами (и почему некоторые думают что это хорошо)
  • V. Об инструментах
    • Об инструментах
  • GTK 4
    • GtkApplication и GtkApplicationWindow
    • Widgets
Powered by GitBook
On this page
  • Шаблонный код заголовка
  • Шаблонный код
  • Конструирование объекта
  • Уничтожение объекта

Was this helpful?

  1. IV. Руководство

Как определить и реализовать новый GObject?

Previousgobject-queryNextОбъектные методы

Last updated 6 years ago

Was this helpful?

Конечно, в основном люди задаются вопросом: как реализовать подкласс GObject. Иногда потому что нужно создать собственную иерархию класса, иногда потому что нужен подкласс одного из виджетов GTK+. Этот раздел сфокусирован над реализацией подтипов GObject. Пример исходного кода связанного с этим разделом может быть найден в исходниках документации, в каталоге sample/gobject:

  • maman-bar.{h|c}: это исходник для объекта который наследует и который показывает как декларируются разные типовые методы объекта.

  • maman-subbar.{h|c}: это исходник для объекта который наследует MamanBar и который показывает как переписать некоторые его родительские методы.

  • maman-foo.{h|c}: это исходник для объекта который наследует и который декларирует сигнал.

  • test.c: это основной исходник который инстанциирует экземпляр типа и осуществляет API.

Шаблонный код заголовка

Первый шаг написания кода для вашего GObject - написать типовой заголовок который содержит необходимые декларации типа, функций и макросов. Каждый из этих элементов является соглашением которому следует не только код GTK+, но также большинство пользователей GObject. Подумайте дважды прежде чем нарушать правила заявленные ниже:

  • Если пользователи немного привыкли к коду GTK+ или любому коду Glib, они будут удивлены вашими нарушениями и привыкание к тому что вы нарушили соглашения займёт время (деньги) и будет их раздражать (это нехорошо)

  • Вы должны понимать что возможно эти соглашения разрабатывали достаточно умные и опытные люди: возможно они были по крайней мере частично правы. Попробуйте преодолеть своё эго.

Выберете согласованное имя для ваших файлов заголовков и исходного кода и придерживайтесь его:

  • использовать тире для отделения префикса от типового имени: maman-bar.h и maman-bar.c (это соглашение используется Nautilus и большинством библиотек GNOME).

  • использовать символ подчеркивания для разделения префикса и типового имени: maman_bar.h и maman_bar.c.

  • Не отделять префикс от типового имени: mamanbar.h и mamanbar.c. (это соглашение используется в GTK+)

Я лично предпочитаю первое соглашение: оно позволяет легче читать имена файлов людям со слабым зрением, таким как я.

Когда вам необходимы некоторые закрытые (внутренние) декларации в нескольких (под)классах, вы можете определить их в закрытом заголовочном файле который часто называют добавляя ключевое слово private к общему имени заголовочных файлов. Например, можно было бы использовать maman-bar-private.h, maman_bar_private.h или mamanbarprivate.h. Обычно такие закрытые заголовочные файлы не устанавливаются.

Основные соглашения для любых заголовков GType описаны в . Большинство кода основанного на GObject также придерживается следующих соглашений: выберете одно из них и придерживайтесь его.

  • Если вы хотите задекларировать тип с именем bar и префиксом maman, имя типового экземпляра MamanBar и его класс MamanBarClass (имена регистрозависимы). Общепринято декларировать это кодом подобно следующему:

    /*
     * Информация о правах и лицензионных соглашениях.
     */
    
    #ifndef MAMAN_BAR_H
    #define MAMAN_BAR_H
    
    /*
     * Потенциально, включите другие заголовочные файлы от которого зависит этот.
     */
    
    /*
     * Напечатайте макроопределения.
     */
    
    typedef struct _MamanBar MamanBar;
    typedef struct _MamanBarClass MamanBarClass;
    
    struct _MamanBar {
      GObject parent;
      /* члены экземпляра */
    };
    
    struct _MamanBarClass {
      GObjectClass parent;
      /* члены класса */
    };
    
    /* используемый MAMAN_BAR_TYPE */
    GType maman_bar_get_type (void);
    
    /*
     * Определения метода.
     */
    
    #endif
  • Большинство GTK+ типов декларируют свои закрытые поля в общих заголовках с комментарием /* private */, рассчитывая на благоразумие пользователей не пытаться играть с этими полями. Поля не отмеченные как закрытые рассматриваются как общие по умолчанию. Комментарий /* protected */ (с семантикой C++) также используются, главным образом в библиотеке GType, в коде написанном Tim Janik.

    struct _MamanBar {
      GObject parent;
    
      /*< private >*/
      int hsize;
    };
  • typedef struct _MamanBarPrivate MamanBarPrivate;
    struct _MamanBar {
      GObject parent;
        
      /*< private >*/
      MamanBarPrivate *priv;
    };

    Помните

    Не используйте имя private, так как это зарегистрированное ключевое слово c++.

    Закрытая структура определяемая в .c файле, инстанциируется в объектной функции init а уничтожается в объектной функции finalize.

    static void
    maman_bar_finalize (GObject *object) {
      MamanBar *self = MAMAN_BAR (object);
      /* do stuff */
      g_free (self->priv);
    }
    
    static void
    maman_bar_init (GTypeInstance *instance, gpointer g_class) {
      MamanBar *self = MAMAN_BAR (instance);
      self->priv = g_new0 (MamanBarPrivate,1);
      /* do stuff */
    }
  • typedef struct _MamanBarPrivate MamanBarPrivate;
    
    struct _MamanBarPrivate {
      int private_field;
    };
    
    static void
    maman_bar_class_init (MamanBarClass *klass)
    {
      ...
      g_type_class_add_private (klass, sizeof (MamanBarPrivate));
      ...
    }
    
    static void
    maman_bar_init (GTypeInstance *instance, gpointer g_class) {
      MamanBar *self = MAMAN_BAR (instance);
      self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MAMAN_BAR_TYPE, MamanBarPrivate);
      /* do stuff */
    }

Наконец, есть различные соглашения включения заголовков. Снова, выберете одно и придерживайтесь ему. Я лично использую любое из двух без разницы, в зависимости от кодовой базы в которой работаю: согласованное правило.

  • Некоторые люди добавляют заголовочный файл с множеством директив #include для перемещения по всем заголовкам необходимым для компиляции кода клиента. Это позволяет коду клиента просто включить #include "maman-bar.h".

  • Другие не делают никаких #include и ожидают что клиент включит #include самостоятельно заголовочные файлы в которых нуждается перед включением вашего заголовочного файла. Это ускоряет компиляцию потому что минимизирует работу препроцессора. Это может быть использовано вместе с повторной декларацией определённых неиспользуемых типов в клиентском коде для минимизации времени компилирования зависимостей и таким образом укорить компиляцию.

Шаблонный код

В вашем коде, первый шаг это включение (#include) необходимых заголовков: в зависимости от вашей стратегии включения заголовков, это может быть просто #include "maman-bar.h" или десятки строк #include заканчивая строкой #include "maman-bar.h":

/*
 * Правовая информация
 * Copyright information
 */

#include "maman-bar.h"

/* Если вы используете Pimpls, включите определение закрытых структур здесь 
 * Некоторые люди создают заголовочные файлы maman-bar-private.h
 * которые включаются в файл maman-bar.c и которые содержат
 * определения для этих закрытых структур.
 */
struct _MamanBarPrivate {
  int member_1;
  /* наполнение */
};

/* 
 * следующие определения
 * forward definitions
 */

Реализуйте maman_bar_get_type и убедитесь что код компилируется:

GType
maman_bar_get_type (void)
{
  static GType type = 0;
  if (type == 0) {
    static const GTypeInfo info = {
      sizeof (MamanBarClass),
      NULL,   /* base_init */
      NULL,   /* base_finalize */
      NULL,   /* class_init */
      NULL,   /* class_finalize */
      NULL,   /* class_data */
      sizeof (MamanBar),
      0,      /* n_preallocs */
      NULL    /* instance_init */
      };
      type = g_type_register_static (G_TYPE_OBJECT,
                                     "MamanBarType",
                                     &info, 0);
    }
    return type;
}

Конструирование объекта

Люди часто запутываются пытаясь конструировать собственный объект GObjects, из-за множества разных способов монтирования в процессе конструирования объекта: трудно сформулировать который является правильным, рекомендованным способом.

Свойства конструкции будут установлены только после всех выполненных функций instance_init. Ссылка объекта не будет возвращена клиенту g_object_new> пока не будут установлены все свойства конструкции.

Также, я рекомендовал бы сначала писать следующий код:

static void
maman_bar_init (GTypeInstance   *instance,
                gpointer         g_class)
{
  MamanBar *self = (MamanBar *)instance;
  self->private = g_new0 (MamanBarPrivate, 1);

  /* Инициализируем все общие и закрытые члены в разумные значения по умолчанию. */
  /* Если вам нужно специфичное свойство конструирования для завершения инициализации,
   * инициализация задерживается пока не установлено свойство. 
   */
}
static void
bar_class_init (MamanBarClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GParamSpec *maman_param_spec;

  gobject_class->set_property = bar_set_property;
  gobject_class->get_property = bar_get_property;

  maman_param_spec = g_param_spec_string ("maman",
                                          "Maman construct prop",
                                          "Set maman's name",
                                          "no-name-set" /* default value */,
                                          G_PARAM_CONSTRUCT_ONLY |G_PARAM_READWRITE);

  g_object_class_install_property (gobject_class,
                                   PROP_MAMAN,
                                   maman_param_spec);
}

Я думаю что правильно останавливать выполнение программы если конструкционное свойство установлено в значение по умолчанию. Это позволит вам перехватить клиентский код который не предаёт разумное значение свойствам конструирования. Конечно вы можете не согласиться с этим, но вы должны иметь для этого веские причины.

Уничтожение объекта

Процессс уничтожения вашего объекта должен быть разбит на две фазы: вы должны отменить и методы распределения и методы финализации класса.

struct _MamanBarPrivate {
  gboolean dispose_has_run;
};

static GObjectClass parent_class = NULL;

static void
bar_dispose (GObject *obj)
{
  MamanBar *self = (MamanBar *)obj;

  if (self->priv->dispose_has_run) {
   /* если распределение уже выполнено, возвращаемся. */
    return;
  }
  /* убеждаемся что распределение не выполняется дважды. */
  object->priv->dispose_has_run = TRUE;

  /* 
   * В распределении, вы должны освободить все типы ссылок из этого объекта
   * которые могут сами содержать ссылки на себя. В основном,
   * самое простое решение освободить ссылки всех членов которыми вы 
   * владеете.
   */

   /* Формируем цепочку до родительского класса */
   G_OBJECT_CLASS (parent_class)->dispose (obj);
}

static void
bar_finalize (GObject *obj)
{
  MamanBar *self = (MamanBar *)obj;

   /* Формируем цепочку до родительского класса */
   G_OBJECT_CLASS (parent_class)->finalize (obj);
}

static void
bar_class_init (BarClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->dispose = bar_dispose;
  gobject_class->finalize = bar_finalize;

  parent_class = g_type_class_peek_parent (klass);
  g_type_class_add_private(klass,sizeof(MamanBarPrivate));
}

static void
maman_bar_init (GTypeInstance   *instance,
                gpointer         g_class)
{
  MamanBar *self = (MamanBar *)instance;
  self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self, BT_TYPE_PATTERN, BtPatternPrivate);
  self->priv->dispose_has_run = FALSE;

}

Добавьте подобный код в ваш GObject, убедитесь что код собирается и выполняется: распределение и финализация должны быть вызваны в течение последнего освобождения ссылки. Возможно что методы объекта могут быть вызваны после выполнения распределения и перед выполнением финализации. GObject не предполагает что это ошибка программы: вы должны изящно обнаружить это и не предупреждая пользователя не допустить краха. Для этого, вам потребуется следующий код в начале каждого объектного метода, чтобы убедиться что объектные данные остаются доступными перед манипулированием ими:

if (self->private->dispose_has_run) {
  /* Распределение было выполнено. Данные больше недоступны. */
  return;
}

Весь код Nautilus и большинство библиотек GNOME используют закрытые косвенные члены, как описано Herb Sutter в его статьях Pimpl (смотрите и : он суммировал разные проблемы лучше меня).

Подобная альтернативная возможность, начиная с версии Glib 2.4, определить закрытую структуру в .c файле, декларируя её как закрытую структуру в maman_bar_class_initиспользуя . Вместо распределения памяти в maman_bar_init указать закрытую область памяти сохранённую в экземпляре позволяя удобный доступ к этой структуре. Таким образом закрытая структура будет прикреплена к каждому вновь созданному объекту системы GObject. Вам не нужно освобождать или распределять закрытую структуру, только объекты или указатели которые она может содержать. Другое преимущество этой версии перед предыдущей в том что это уменьшает фрагментацию памяти, так как общие и закрытые части памяти экземпляра распределяются одновременно.

показывает что функции обеспечиваемые пользователем вызываются в течение инстанциации объекта и в каком порядке они вызываются. Пользователь ищущий эквивалент простой функции конструктора C++ должен использовать instance_init метод. Он будет вызван после всех родительских функций instance_init. Эта функция не принимает произвольных параметров конструирования (как в C++), но если ваш объект нуждается в произвольных параметрах для завершения инициализации, вы можете использовать свойства конструирования.

Убедитесь что вы установили maman_bar_init как типовую функцию instance_init в maman_bar_get_type. Убедитесь что код собирается и выполняется: создайте экземпляр объекта и убедитесь что maman_bar_init вызывается (добавьте в него вызов ).

Теперь, если вам нужны специальные свойства конструирования, установите свойства в функцию class_init, отмените установленные и полученные методы и реализуйте получение и установку методов как описано в . Убедитесь что эти свойства используются только конструируя , установив флаговое поле спецификации параметра в значение G_PARAM_CONSTRUCT_ONLY: это поможет GType убедится что свойства не установятся позже снова злонамеренным пользовательским кодом.

Если вам это нужно, убедитесь что вы можете собрать и запустить код подобный коду показанному выше. Убедитесь что ваши конструкционные свойства могут правильно устанавливаться в течение конструирования, убедитесь что вы не можете установить их впоследствие и убедитесь что без вызова с необходимыми свойствами конструирования, они будут инициализированы значениями по умолчанию.

Некоторые люди иногда нуждаются в конструировании собственных объектов, но только после установки конструкционных свойств. Это возможно благодаря использованию классовому методу конструирования как описано в . Однако, я не вижу никаких разумных причин использовать эту особенность. Также, для инициализации вашего экземпляра объекта, используйте функцию base_init и конструкционные свойства по умолчанию.

Опять, часто сложно выяснить какой механизм использовать для обработки процесса уничтожения объекта: когда сделан последний вызов функции , многое происходит как описано в .

GObject
GObject
“Conventions”
Compilation Firewalls
The Fast Pimpl Idiom
g_type_class_add_private
Table 4, “g_object_new”
g_print
the section called “Object properties”
GParamSpec
g_object_new
the section called “Object instanciation”
g_object_unref
Table 5, “g_object_unref”