Как создавать и использовать сигналы
Last updated
Was this helpful?
Last updated
Was this helpful?
Система сигналов которая была создана в GType довольно сложная и гибкая: она позволяет пользователям подключать в основном цикле любое количество callback-функций (реализовано в любом языке для которого есть привязка) [] к любому сигналу и останавливать эмиссию любого сигнала в любом состоянии. Эта гибкость делает возможным использование GSignal намного шире чем простое издание событий которые могут быть получены многочисленными клиентами.
Самое основное использование сигналов это осуществление простого уведомления о произошедшем событии: например, если вы имеете объект MamanFile, и если этот объект имеет написанный метод, можно получать уведомление каждый раз когда кто-нибудь использует этот метод. Код ниже показывает как пользователь может подключить callback-функцию к созданному сигналу. Полный код этого примера расположен в sample/signal/maman-file.{h|c}
и в sample/signal/test.c
Сигнал MamanFile регистрируется в функции class_init:
а издаётся сигнал в maman_file_write
:
Сигнатура обработчика сигнала в примере выше определена как g_cclosure_marshal_VOID__VOID
. Это название следует простому соглашению - кодировать параметр функции и тип возвращаемого значения в имени функции. Определено что, значение перед двойным подчёркиванием это тип возвращаемого значения, в то время как значение(я) после двойного подчёркивания обозначает тип параметров. Заголовок gobject/gmarshal.h
определяет основной набор необходимых замыканий которые можно использовать.
Сигналы 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
которая переключает эмиссию сигнала:
Код выше генерирует следующий вывод на моей машине:
Следующий код показывает декларацию классовой сструктуры MamanFileSimple которая содержит указатель write
функции.
Указатель write
функции инициализированной в функции class_init объекта для default_write_signal_handler
:
Не смотря на то, что полный код для этого обработчика по умолчанию выглядит меньше 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
.
Как показано выше, вы можете безопасно установить детальный параметр в ноль если вы не знаете для чего его использовать. Обсуждение того где он может использоваться, смотрите
[] python callback-функции могут быть подключены к любому сигналу в любом C-подобном GObject.
Клиентский код (представленный ниже и в sample/signal/test.c
) может теперь подключать обработчики сигнала перед и после завершения записи файла: так как обработчик сигнала по умолчанию, который делает саму запись, выполняется в течение фазы RUN_LAST эмиссии сигнала, он будет выполнен после всех обработчиков подключенных с помощью и перед обработчиками подключенными с помощью . Если вы намерены написать GObject который издаёт сигналы, я рекомендовал бы вам создавать все ваши сигналы с G_SIGNAL_RUN_LAST чтобы пользователи могли иметь максимальную гибкость при получении события. Здесь мы объединили его с G_SIGNAL_NO_RECURSE и G_SIGNAL_NO_HOOKS чтобы гарантировать что пользователи не будут делать ничего сверхъестественного с нашим GObject. Я строго советую вам делать тоже самое если вы действительно не знаете почему (если бы вы знали внутреннюю работу GSignal вы бы это не читали).
По многим историческим причинам связанным с тем что предок GObject используется для работы в GTK+ версий 1.x, есть намного более простой[] способ создания сигнала с обработчиком по умолчанию, чем создавать замыкание вручную и использовать .
Например, может использоваться для создания сигнала который использует обработчик по умолчанию сохранённый в структуре класса объекта. Конкретнее, структура класса содержит указатель функции который доступен в течение эмиссии сигнала для вызова обработчика по умолчанию, а пользователь как ожидается обеспечит для смещение сначала классовой сструктуры для указания функции.[]
Наконец, сигнал создаётся с помощью в той же функции class_init:
Примечателен здесь 4-ый аргумент функции: он рассчитывается с помощью макроса G_STRUCT_OFFSET
указывая смещение члена write от начала классовой сструктурыMamanFileSimpleClass.[]
Если вы не знаете какой метод использовать, я советовал бы вам второй который вызывает а не : он лучше для написания кода который похож на большую часть кода GTK+/GObject чем делать это собственным способом. Однако теперь вы знаете как это сделать.
[] Я лично думаю что этот метод ужасно запутанный: он добавляет новую неопределённость которая излишне усложняет код. Однако, раз этот метод широко используется во всём коде GTK+ и GObject, читатель должен об этом знать. Причина по которой этот метод используется в GTK+ связана с фактом что предок GObject не обеспечивал других способов создания сигналов с обработчиками по умолчанию. Некоторые люди пытались оправдать этот способ тем что он быстрее и лучше (У меня большие сомнения по поводу утверждения о быстроте. Честно говоря, и фраза о "лучше" тоже большая для меня загадка ;-). Я думаю что большинство копируют похожий код и не задумываются над этим. Вероятно лучше оставить эти специфичные пустяки в области хакерских легенд...
[] Я хотел бы заметить что причина по которой обработчик сигнала по умолчанию везде именуется как class_closure заключается в том что фактически это действительно указатель функции хранящийся в классовой структуре.
[] GSignal использует это смещение для создания специальной оболочки замыкания которая сначала получает целевой указатель функции перед её вызовом.