GLib
  • Initial page
  • I. Концепция
    • Background
    • Типы данных и программирование
  • Динамическая система типов Glib
    • Введение
    • Функции копирования
    • Соглашения
    • Неинстанциируемые не классифицированные базовые типы
    • Инстанциируемые классифицируемые типы: объекты
    • Неинстанциированные классифицированные типы: Интерфейсы.
  • Основной класс GObject
    • Введение
    • Инстанциация объекта
    • Объектное управление памятью
    • Свойства объекта
  • Система сообщений GObject
    • Замыкания
    • Сигналы
  • II. Описание API
    • GType
    • GTypePlugin
    • GTypeModule
    • GObject часть 1
    • GObject часть 2
    • Enums and Flags
    • GBoxed
    • Generic Values
    • Parameters and Values часть 1
    • Parameters and Values часть 2
    • Parameters and Values часть 3, ага
    • Value arrays
  • III. Описание инструментов
    • glib-mkenums
    • glib-genmarshal
    • gobject-query
  • IV. Руководство
    • Как определить и реализовать новый GObject?
    • Объектные методы
    • Как определять и реализовывать Интерфейсы?
    • Как создавать и использовать сигналы
    • Как пользователи могут злоупотреблять сигналами (и почему некоторые думают что это хорошо)
  • V. Об инструментах
    • Об инструментах
  • GTK 4
    • GtkApplication и GtkApplicationWindow
    • Widgets
Powered by GitBook
On this page
  • Пример использования сигналов
  • Как обеспечить большую гибкость для пользователей?

Was this helpful?

  1. IV. Руководство

Как создавать и использовать сигналы

PreviousКак определять и реализовывать Интерфейсы?NextКак пользователи могут злоупотреблять сигналами (и почему некоторые думают что это хорошо)

Last updated 6 years ago

Was this helpful?

Система сигналов которая была создана в GType довольно сложная и гибкая: она позволяет пользователям подключать в основном цикле любое количество callback-функций (реализовано в любом языке для которого есть привязка) [] к любому сигналу и останавливать эмиссию любого сигнала в любом состоянии. Эта гибкость делает возможным использование 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:

void maman_file_write (MamanFile *self, guint8 *buffer, guint32 size)
{
  /* Сначала записываем данные. */
  /* Затем, сообщаем пользователю о записанных данных. */
  g_signal_emit (self, MAMAN_FILE_GET_CLASS (self)->write_signal_id,
                 0 /* details */, 
                 NULL);
}

Сигнатура обработчика сигнала в примере выше определена как g_cclosure_marshal_VOID__VOID. Это название следует простому соглашению - кодировать параметр функции и тип возвращаемого значения в имени функции. Определено что, значение перед двойным подчёркиванием это тип возвращаемого значения, в то время как значение(я) после двойного подчёркивания обозначает тип параметров. Заголовок gobject/gmarshal.h определяет основной набор необходимых замыканий которые можно использовать.

Как обеспечить большую гибкость для пользователей?

Сигналы 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);
}
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

Как большинство людей делают тоже самое с меньшим количеством кода

Следующий код показывает декларацию классовой сструктуры 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;
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);

Не смотря на то, что полный код для этого обработчика по умолчанию выглядит меньше 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 использует это смещение для создания специальной оболочки замыкания которая сначала получает целевой указатель функции перед её вызовом.

16
“The detail argument”
16
g_signal_connect
g_signal_connect_after
17
g_signal_newv
g_signal_new
g_signal_new
18
g_signal_new
19
g_signal_new
g_signal_newv
17
18
19