| |
Справочное описание 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 использует это смещение для создания специальной оболочки замыкания которая сначала получает целевой указатель функции перед её вызовом.
Закладки на сайте Проследить за страницей |
Created 1996-2024 by Maxim Chirkov Добавить, Поддержать, Вебмастеру |