Как определить, был ли уничтожен виджет GTK

У меня есть код Gtk+, написанный на C, который выполняет анимацию с использованием Cairo и таймера. В большинстве случаев, когда я нажимаю на значок закрытия приложения, я получаю следующее сообщение на терминале:

Gtk-CRITICAL **: gtk_widget_queue_draw: утверждение `GTK_IS_WIDGET (виджет)' не удалось

Теперь я предполагаю, что это происходит, потому что в тот момент, когда я закрываю приложение, срабатывает таймер, и виджет главного окна доступен, но с тех пор был уничтожен. Каков обычный метод определения того, действителен ли виджет Gtk и на него можно ссылаться?

Код нарушения находится здесь:

gboolean rotate_cb( void *degrees )
{
    rotation += DegreesToRadians((*(int*)(degrees)));
    // Tell our window that it should repaint itself (ie. emit an expose event)
    /* need to only call gtk_widget_queue_draw() if window is still valid / exists */
    gtk_widget_queue_draw(window);
    return( TRUE );
}

Я предполагаю, что должен быть какой-то способ проверить, активен ли и действителен ли window?


person Chimera    schedule 14.06.2012    source источник


Ответы (3)


Ваша проблема довольно тонкая. Обычно это происходит из-за правил владения и уничтожения GObject/GtkObject. Напомню им:

  • GObject просто ссылаются на количество. Они уничтожаются, когда счетчик достигает 0. У вновь созданного объекта есть счетчик 1.
  • GInitiallyUnowned также ссылаются на счетчик, и они также уничтожаются, когда счетчик достигает 0. Но вновь созданный объект имеет плавающий счетчик. Это означает, что при первом увеличении счетчика он на самом деле не увеличивается, а плавающий счетчик погружается, то есть преобразуется в обычную ссылку.

GtkObjects являются GInitiallyUnowned объектами, поэтому они имеют волшебный плавающий счет.

Но вы, наверное, все это знаете... Теперь мой вопрос:

Кто владеет счетчиком видимого основного GtkWindow?

Это на самом деле просто, фреймворк GTK имеет их список и сохраняет ссылку на каждый видимый GtkWindow. Но тогда другой вопрос:

Когда инфраструктура GTK освобождает ссылки своего GtkWindow?

Вы помните функцию gtk_widget_destroy() и сигнал destroy? Они как раз для этого: когда вы хотите удалить GtkWindow верхнего уровня, вы вызываете gtk_widget_destroy(), он активирует сигнал destroy, полученный фреймворком GTK, который удаляет фактическое окно и освобождает его ссылку на объект.

И вот причина вашей проблемы: если структура GTK сохраняет единственную существующую ссылку на GtkWindow, объект фактически освобождается. Если затем ваш таймер попытается получить к нему доступ, он потерпит неудачу, потому что окна больше нет.

И вот, наконец, приходит (надеюсь) решение:

  • Вызовите g_object_ref()/g_object_ref_sink() в окне, когда вы запускаете таймер. Также зарегистрируйте обработчик сигнала destroy окна.
  • В обработчике сигнала destroy окна: вызвать g_object_unref() для окна и остановить таймер.

Естественно, это частичное решение тоже должно работать, потому что окно не будет уничтожено без отправки сигнала destroy:

  • Зарегистрируйте обработчик сигнала destroy окна.
  • В обработчике сигнала destroy окна: остановить таймер.

Но считается хорошей практикой увеличивать счетчик ссылок объектов, когда вы фактически сохраняете указатель на них.

person rodrigo    schedule 14.06.2012
comment
Спасибо Родриго за очень подробный ответ. Я многому научился и ценю это. - person Chimera; 15.06.2012
comment
Очень поучительный ответ, +1! - person Nelson; 19.02.2017

Это невозможно, потому что вы запрашиваете состояние объекта, которого больше нет в памяти, то есть какого-то неопределенного фрагмента памяти. Такая функция может выйти из строя, например. если по тому же адресу памяти создан другой виджет. В принципе, вы можете просто использовать этот макрос GTK_IS_WIDGET(), но он не гарантирует правильную работу, даже если чаще всего так и будет.

Правильный способ решить вашу проблему - вместо этого удалить обратный вызов rotate_cb(), когда window будет уничтожен. Для этого в классе GtkWidget есть сигнал "уничтожить". Итак, вы должны подключиться к «уничтожить» и в этом обработчике удалить rotate_cb() (или установить какой-либо флаг, который заставляет rotate_cb() ничего не делать).

person doublep    schedule 14.06.2012

g_object_weak_ref() также полезен для этих случаев

Слабые ссылки используются для уведомления о завершении работы над объектом.

Вы можете подключить слабый обратный вызов ref (например, widget_destroy_cb), чтобы отключить rotate_cb от виджета до его уничтожения, поэтому rotate_cb не следует вызывать после уничтожения виджета.

person L. Lopez    schedule 15.06.2012