Как определить и реализовать новый GObject?
Last updated
Was this helpful?
Last updated
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
(имена регистрозависимы). Общепринято декларировать это кодом подобно следующему:
Большинство GTK+ типов декларируют свои закрытые поля в общих заголовках с комментарием /* private */, рассчитывая на благоразумие пользователей не пытаться играть с этими полями. Поля не отмеченные как закрытые рассматриваются как общие по умолчанию. Комментарий /* protected */ (с семантикой C++) также используются, главным образом в библиотеке GType, в коде написанном Tim Janik.
Помните
Не используйте имя private
, так как это зарегистрированное ключевое слово c++.
Закрытая структура определяемая в .c файле, инстанциируется в объектной функции init
а уничтожается в объектной функции finalize
.
Наконец, есть различные соглашения включения заголовков. Снова, выберете одно и придерживайтесь ему. Я лично использую любое из двух без разницы, в зависимости от кодовой базы в которой работаю: согласованное правило.
Некоторые люди добавляют заголовочный файл с множеством директив #include для перемещения по всем заголовкам необходимым для компиляции кода клиента. Это позволяет коду клиента просто включить #include "maman-bar.h".
Другие не делают никаких #include и ожидают что клиент включит #include самостоятельно заголовочные файлы в которых нуждается перед включением вашего заголовочного файла. Это ускоряет компиляцию потому что минимизирует работу препроцессора. Это может быть использовано вместе с повторной декларацией определённых неиспользуемых типов в клиентском коде для минимизации времени компилирования зависимостей и таким образом укорить компиляцию.
В вашем коде, первый шаг это включение (#include) необходимых заголовков: в зависимости от вашей стратегии включения заголовков, это может быть просто #include "maman-bar.h" или десятки строк #include заканчивая строкой #include "maman-bar.h":
Реализуйте maman_bar_get_type
и убедитесь что код компилируется:
Люди часто запутываются пытаясь конструировать собственный объект GObjects, из-за множества разных способов монтирования в процессе конструирования объекта: трудно сформулировать который является правильным, рекомендованным способом.
Свойства конструкции будут установлены только после всех выполненных функций instance_init. Ссылка объекта не будет возвращена клиенту g_object_new>
пока не будут установлены все свойства конструкции.
Также, я рекомендовал бы сначала писать следующий код:
Я думаю что правильно останавливать выполнение программы если конструкционное свойство установлено в значение по умолчанию. Это позволит вам перехватить клиентский код который не предаёт разумное значение свойствам конструирования. Конечно вы можете не согласиться с этим, но вы должны иметь для этого веские причины.
Процессс уничтожения вашего объекта должен быть разбит на две фазы: вы должны отменить и методы распределения и методы финализации класса.
Добавьте подобный код в ваш GObject, убедитесь что код собирается и выполняется: распределение и финализация должны быть вызваны в течение последнего освобождения ссылки. Возможно что методы объекта могут быть вызваны после выполнения распределения и перед выполнением финализации. GObject не предполагает что это ошибка программы: вы должны изящно обнаружить это и не предупреждая пользователя не допустить краха. Для этого, вам потребуется следующий код в начале каждого объектного метода, чтобы убедиться что объектные данные остаются доступными перед манипулированием ими:
Весь код 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 и конструкционные свойства по умолчанию.
Опять, часто сложно выяснить какой механизм использовать для обработки процесса уничтожения объекта: когда сделан последний вызов функции , многое происходит как описано в .