Справочное описание 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
(имена регистрозависимы). Общепринято декларировать это кодом подобно следующему:
/*
* Информация о правах и лицензионных соглашениях.
*/
#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;
};
Весь код Nautilus и большинство библиотек GNOME используют закрытые косвенные члены, как описано Herb Sutter в его статьях Pimpl (смотрите Compilation Firewalls и The Fast Pimpl Idiom : он суммировал разные проблемы лучше меня).
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 */
}
Подобная альтернативная возможность, начиная с версии Glib 2.4, определить закрытую структуру в .c файле,
декларируя её как закрытую структуру в maman_bar_class_init
используя
g_type_class_add_private
.
Вместо распределения памяти в maman_bar_init
указать закрытую область памяти сохранённую в
экземпляре позволяя удобный доступ к этой структуре.
Таким образом закрытая структура будет прикреплена к каждому вновь созданному объекту системы GObject.
Вам не нужно освобождать или распределять закрытую структуру, только объекты или указатели которые она может содержать.
Другое преимущество этой версии перед предыдущей в том что это уменьшает фрагментацию памяти, так как общие и закрытые
части памяти экземпляра распределяются одновременно.
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 самостоятельно заголовочные файлы в которых нуждается перед включением вашего заголовочного файла. Это ускоряет компиляцию потому что минимизирует работу препроцессора. Это может быть использовано вместе с повторной декларацией определённых неиспользуемых типов в клиентском коде для минимизации времени компилирования зависимостей и таким образом укорить компиляцию.