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

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

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

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

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

  • 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 описаны в “Conventions”. Большинство кода основанного на GObject также придерживается следующих соглашений: выберете одно из них и придерживайтесь его.

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

  • Большинство GTK+ типов декларируют свои закрытые поля в общих заголовках с комментарием /* private */, рассчитывая на благоразумие пользователей не пытаться играть с этими полями. Поля не отмеченные как закрытые рассматриваются как общие по умолчанию. Комментарий /* protected */ (с семантикой C++) также используются, главным образом в библиотеке GType, в коде написанном Tim Janik.

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

    Помните

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Last updated

Was this helpful?