Справочное описание GObject |
---|
Сигналы GObject могут использоваться для обеспечения большей гибкости чем механизм уведомления об изменении файла рассмотренный в предыдущем примере. Одна из ключевых идей - сделать процесс записи данных в файл частью процесса эмиссии сигнала позволив пользователю получать уведомления либо перед либо после записи данных в файл.
Для интеграции процесса записи данных в файл с механизмом эмиссии сигнала, мы можем зарегистрировать классовое замыкание по умолчанию для этого сигнала которое будет вызываться в течение эмиссии сигнала, просто как любой другой подключаемый пользователем обработчик сигнала.
Первый шаг реализации этой идеи - изменить сигнатуру сигнала: мы должны разместить буфер для записи и его размер.
Для этого мы используем собственный маршаллер который будет сгенерирован с помощью специальной утилиты glib.
Таким образом создаём файл с именем marshall.list
который содержит следующую строку:
VOID:POINTER,UINT
и используем Makefile поставляемый в sample/signal/Makefile
для генерации файла с именем
maman-file-complex-marshall.c
. Этот C файл включаем в maman-file-complex.c
.
Как только маршаллер создан, мы регистрируем сигнал и его маршаллер в функции class_init объекта
MamanFileComplex (полный исходный код этого объекта включён в
sample/signal/maman-file-complex.{h|c}
):
GClosure *default_closure;
GType param_types[2];
default_closure = g_cclosure_new (G_CALLBACK (default_write_signal_handler),
(gpointer)0xdeadbeaf /* user_data */,
NULL /* destroy_data */);
param_types[0] = G_TYPE_POINTER;
param_types[1] = G_TYPE_UINT;
klass->write_signal_id =
g_signal_newv ("write",
G_TYPE_FROM_CLASS (g_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
default_closure /* class closure */,
NULL /* accumulator */,
NULL /* accu_data */,
maman_file_complex_VOID__POINTER_UINT,
G_TYPE_NONE /* return_type */,
2 /* n_params */,
param_types /* param_types */);
Код показанный выше создаёт замыкание которое содержит код полной записи файла. Это замыкание регистрируется как значение по умолчанию class_closure вновь созданного сигнала.
Конечно, вы должны полностью реализовать код для замыкания по умолчанию, так как я создал только скелет:
static void
default_write_signal_handler (GObject *obj, guint8 *buffer, guint size, gpointer user_data)
{
g_assert (user_data == (gpointer)0xdeadbeaf);
/* Здесь мы вызываем реальную запись файла. */
g_print ("default signal handler: 0x%x %u\n", buffer, size);
}
Наконец, код клиента должен вызвать функцию maman_file_complex_write
которая переключает
эмиссию сигнала:
void maman_file_complex_write (MamanFileComplex *self, guint8 *buffer, guint size)
{
/* trigger event */
g_signal_emit (self,
MAMAN_FILE_COMPLEX_GET_CLASS (self)->write_signal_id,
0, /* details */
buffer, size);
}
Клиентский код (представленный ниже и в sample/signal/test.c
) может теперь подключать обработчики сигнала
перед и после завершения записи файла: так как обработчик сигнала по умолчанию, который делает саму запись, выполняется в течение фазы
RUN_LAST эмиссии сигнала, он будет выполнен после всех обработчиков подключенных с помощью
g_signal_connect
и перед обработчиками подключенными с помощью
g_signal_connect_after
.
Если вы намерены написать GObject который издаёт сигналы, я рекомендовал бы вам создавать все ваши сигналы
с G_SIGNAL_RUN_LAST чтобы пользователи могли иметь максимальную гибкость при получении события.
Здесь мы объединили его с G_SIGNAL_NO_RECURSE и G_SIGNAL_NO_HOOKS чтобы гарантировать что пользователи не будут делать ничего
сверхъестественного с нашим GObject. Я строго советую вам делать тоже самое если вы действительно не знаете почему
(если бы вы знали внутреннюю работу GSignal вы бы это не читали).
static void complex_write_event_before (GObject *file, guint8 *buffer, guint size, gpointer user_data)
{
g_assert (user_data == NULL);
g_print ("Complex Write event before: 0x%x, %u\n", buffer, size);
}
static void complex_write_event_after (GObject *file, guint8 *buffer, guint size, gpointer user_data)
{
g_assert (user_data == NULL);
g_print ("Complex Write event after: 0x%x, %u\n", buffer, size);
}
static void test_file_complex (void)
{
guint8 buffer[100];
GObject *file;
file = g_object_new (MAMAN_FILE_COMPLEX_TYPE, NULL);
g_signal_connect (G_OBJECT (file), "write",
(GCallback)complex_write_event_before,
NULL);
g_signal_connect_after (G_OBJECT (file), "write",
(GCallback)complex_write_event_after,
NULL);
maman_file_complex_write (MAMAN_FILE_COMPLEX (file), buffer, 50);
g_object_unref (G_OBJECT (file));
}
Код выше генерирует следующий вывод на моей машине:
Complex Write event before: 0xbfffe280, 50
default signal handler: 0xbfffe280 50
Complex Write event after: 0xbfffe280, 50
По многим историческим причинам связанным с тем что предок GObject используется для работы в GTK+ версий 1.x,
есть намного более простой[17]
способ создания сигнала с обработчиком по умолчанию, чем создавать замыкание вручную и использовать
g_signal_newv
.
Например, g_signal_new
может использоваться для создания
сигнала который использует обработчик по умолчанию сохранённый в структуре класса объекта. Конкретнее, структура класса содержит
указатель функции который доступен в течение эмиссии сигнала для вызова обработчика по умолчанию, а пользователь как ожидается
обеспечит для g_signal_new
смещение сначала классовой
сструктуры для указания функции.[18]
Следующий код показывает декларацию классовой сструктуры MamanFileSimple которая содержит указатель
write
функции.
struct _MamanFileSimpleClass {
GObjectClass parent;
guint write_signal_id;
/* обработчик сигнала по умолчанию */
void (*write) (MamanFileSimple *self, guint8 *buffer, guint size);
};
Указатель write
функции инициализированной в функции class_init объекта для
default_write_signal_handler
:
static void
maman_file_simple_class_init (gpointer g_class,
gpointer g_class_data)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (g_class);
MamanFileSimpleClass *klass = MAMAN_FILE_SIMPLE_CLASS (g_class);
klass->write = default_write_signal_handler;
Наконец, сигнал создаётся с помощью g_signal_new
в той же функции class_init:
klass->write_signal_id =
g_signal_new ("write",
G_TYPE_FROM_CLASS (g_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
G_STRUCT_OFFSET (MamanFileSimpleClass, write),
NULL /* accumulator */,
NULL /* accu_data */,
maman_file_complex_VOID__POINTER_UINT,
G_TYPE_NONE /* return_type */,
2 /* n_params */,
G_TYPE_POINTER,
G_TYPE_UINT);
Примечателен здесь 4-ый аргумент функции: он рассчитывается с помощью макроса G_STRUCT_OFFSET
указывая смещение члена write от начала классовой сструктуры
MamanFileSimpleClass.[19]
Не смотря на то, что полный код для этого обработчика по умолчанию выглядит меньше clutered как показано в
sample/signal/maman-file-simple.{h|c}
, он содержит много тонкостей.
Основная тонкость которую должен знать каждый - сигнатура обработчика по умолчанию созданного таким способом
не имеет аргумента user_data:
default_write_signal_handler
в
sample/signal/maman-file-complex.c
отличается от
sample/signal/maman-file-simple.c
.
Если вы не знаете какой метод использовать, я советовал бы вам второй который вызывает
g_signal_new
а не
g_signal_newv
:
он лучше для написания кода который похож на большую часть кода GTK+/GObject чем делать это собственным способом.
Однако теперь вы знаете как это сделать.
[17] Я лично думаю что этот метод ужасно запутанный: он добавляет новую неопределённость которая излишне усложняет код. Однако, раз этот метод широко используется во всём коде GTK+ и GObject, читатель должен об этом знать. Причина по которой этот метод используется в GTK+ связана с фактом что предок GObject не обеспечивал других способов создания сигналов с обработчиками по умолчанию. Некоторые люди пытались оправдать этот способ тем что он быстрее и лучше (У меня большие сомнения по поводу утверждения о быстроте. Честно говоря, и фраза о "лучше" тоже большая для меня загадка ;-). Я думаю что большинство копируют похожий код и не задумываются над этим. Вероятно лучше оставить эти специфичные пустяки в области хакерских легенд...
[18] Я хотел бы заметить что причина по которой обработчик сигнала по умолчанию везде именуется как class_closure заключается в том что фактически это действительно указатель функции хранящийся в классовой структуре.
[19] GSignal использует это смещение для создания специальной оболочки замыкания которая сначала получает целевой указатель функции перед её вызовом.