Как создавать и использовать сигналы
Система сигналов которая была создана в GType довольно сложная и гибкая: она позволяет пользователям подключать в основном цикле любое количество callback-функций (реализовано в любом языке для которого есть привязка) [16] к любому сигналу и останавливать эмиссию любого сигнала в любом состоянии. Эта гибкость делает возможным использование GSignal намного шире чем простое издание событий которые могут быть получены многочисленными клиентами.
Пример использования сигналов
Самое основное использование сигналов это осуществление простого уведомления о произошедшем событии: например, если вы имеете объект MamanFile, и если этот объект имеет написанный метод, можно получать уведомление каждый раз когда кто-нибудь использует этот метод. Код ниже показывает как пользователь может подключить callback-функцию к созданному сигналу. Полный код этого примера расположен в sample/signal/maman-file.{h|c} и в sample/signal/test.c
file = g_object_new (MAMAN_FILE_TYPE, NULL);
g_signal_connect (G_OBJECT (file), "write",
(GCallback)write_event,
NULL);
maman_file_write (file, buffer, 50);Сигнал MamanFile регистрируется в функции class_init:
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,
NULL /* class closure */,
NULL /* accumulator */,
NULL /* accu_data */,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE /* return_type */,
0 /* n_params */,
NULL /* param_types */);а издаётся сигнал в maman_file_write:
Как показано выше, вы можете безопасно установить детальный параметр в ноль если вы не знаете для чего его использовать. Обсуждение того где он может использоваться, смотрите “The detail argument”
Сигнатура обработчика сигнала в примере выше определена как g_cclosure_marshal_VOID__VOID. Это название следует простому соглашению - кодировать параметр функции и тип возвращаемого значения в имени функции. Определено что, значение перед двойным подчёркиванием это тип возвращаемого значения, в то время как значение(я) после двойного подчёркивания обозначает тип параметров. Заголовок gobject/gmarshal.h определяет основной набор необходимых замыканий которые можно использовать.
[16] python callback-функции могут быть подключены к любому сигналу в любом C-подобном GObject.
Как обеспечить большую гибкость для пользователей?
Сигналы GObject могут использоваться для обеспечения большей гибкости чем механизм уведомления об изменении файла рассмотренный в предыдущем примере. Одна из ключевых идей - сделать процесс записи данных в файл частью процесса эмиссии сигнала позволив пользователю получать уведомления либо перед либо после записи данных в файл.
Для интеграции процесса записи данных в файл с механизмом эмиссии сигнала, мы можем зарегистрировать классовое замыкание по умолчанию для этого сигнала которое будет вызываться в течение эмиссии сигнала, просто как любой другой подключаемый пользователем обработчик сигнала.
Первый шаг реализации этой идеи - изменить сигнатуру сигнала: мы должны разместить буфер для записи и его размер. Для этого мы используем собственный маршаллер который будет сгенерирован с помощью специальной утилиты glib. Таким образом создаём файл с именем marshall.list который содержит следующую строку:
и используем Makefile поставляемый в sample/signal/Makefile для генерации файла с именем maman-file-complex-marshall.c. Этот C файл включаем в maman-file-complex.c.
Как только маршаллер создан, мы регистрируем сигнал и его маршаллер в функции class_init объекта MamanFileComplex (полный исходный код этого объекта включён вsample/signal/maman-file-complex.{h|c}):
Код показанный выше создаёт замыкание которое содержит код полной записи файла. Это замыкание регистрируется как значение по умолчанию class_closure вновь созданного сигнала.
Конечно, вы должны полностью реализовать код для замыкания по умолчанию, так как я создал только скелет:
Наконец, код клиента должен вызвать функцию maman_file_complex_write которая переключает эмиссию сигнала:
Клиентский код (представленный ниже и в 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 вы бы это не читали).
Код выше генерирует следующий вывод на моей машине:
Как большинство людей делают тоже самое с меньшим количеством кода
По многим историческим причинам связанным с тем что предок GObject используется для работы в GTK+ версий 1.x, есть намного более простой[17] способ создания сигнала с обработчиком по умолчанию, чем создавать замыкание вручную и использовать g_signal_newv.
Например, g_signal_new может использоваться для создания сигнала который использует обработчик по умолчанию сохранённый в структуре класса объекта. Конкретнее, структура класса содержит указатель функции который доступен в течение эмиссии сигнала для вызова обработчика по умолчанию, а пользователь как ожидается обеспечит для g_signal_newсмещение сначала классовой сструктуры для указания функции.[18]
Следующий код показывает декларацию классовой сструктуры MamanFileSimple которая содержит указатель write функции.
Указатель write функции инициализированной в функции class_init объекта для default_write_signal_handler:
Наконец, сигнал создаётся с помощью g_signal_new в той же функции class_init:
Примечателен здесь 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 использует это смещение для создания специальной оболочки замыкания которая сначала получает целевой указатель функции перед её вызовом.
Last updated
Was this helpful?