LINUX.ORG.RU

GTK приложение падает из-за вызова gtk_widget_destroy


0

0

Господа... :)

Столкнулся с довольно неприятной проблемой. Суть ее сводится к тому, что
если в обработчике сигнала "changed" GtkTreeSelection выполнить
gtk_widget_destroy для GtkTreeView этого GtkTreeSelection'а, то приложение
падает с Segmentation fault'ом.

Насколько я понимаю, это происходит из-за того, что GTK после вызова
обработчика производит какие-то действия с
GtkTreeView/GtkTreeSelection/GtkTreeModel, а т. к. я уничтожаю GtkTreeView
в обработчике сигнала, то GTK обращается по старому указателю на уже
несуществующий объект. Может быть я что-то не так делаю? Подскажите
пожалуйста, как можно избежать данной ошибки.

Вот пример приложения, которое аварийно завершается, если щелкнуть мышью по строке
GtkTreeView:
    #include <gtk/gtk.h>

    GtkWidget *treeview;

    void change_callback(GtkTreeSelection *treeselection, gpointer user_data)
    {
        gtk_widget_destroy(treeview);
    }

    int main(int argc, char *argv[])
    {
        GtkWidget *window;
        GtkWidget *vbox;
        GtkTreeIter iter;
        GtkTreeModel *items_model;
        GtkCellRenderer *renderer;

        gtk_init(&argc, &argv);

        window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
        vbox = gtk_vbox_new(FALSE, 5);
        gtk_container_add(GTK_CONTAINER(window), vbox);

        items_model = GTK_TREE_MODEL(gtk_list_store_new(1, G_TYPE_INT));
        gtk_list_store_append(GTK_LIST_STORE(items_model), &iter);
        gtk_list_store_set(GTK_LIST_STORE(items_model), &iter, 0, 1, -1);

        treeview = gtk_tree_view_new_with_model(items_model);
        g_object_unref (items_model);
        renderer = gtk_cell_renderer_combo_new();
        gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1, "Name", renderer, "text", 0, NULL);
        g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)))
, "changed", G_CALLBACK(change_callback), NULL);
        gtk_box_pack_start(GTK_BOX(vbox), treeview, FALSE, FALSE, 0);

        gtk_widget_show_all (window);

        gtk_main();
    }

Ответ на: комментарий от anonymous

> думаю надо его вначале g_object_unref

Кстати (чисто теоретически), если сделать g_object_unref для виджета, который находится в контейнере, то скорее всего изначально ref у него 1, а после unref станет 0. Но контейнер всё равно будет считать виджет своим чайлдом, что некорректно.

Bohtvaroh ★★★★
()
Ответ на: комментарий от anonymous

g_object_unref не помогает, да и, по-моему, не должен.

> а зачем вы его руками уничтожаете ?
У меня программа работает с базой данных. Во время выделения строк в одном TreeView производится запрос к базе данных и меняется содержимое другого TreeView. Если вдруг во время этого запроса происходит какая-то ошибка базы данных, мне необходимо закрыть все окно (но оставить другие окна приложения в рабочем состоянии). Поэтому я вызываю функцию gtk_widget_destroy(window), которая закрывает окно и все присоединенные к нему виджеты, в том числе и наш TreeView.

KonishchevDmitry
() автор топика
Ответ на: комментарий от Bohtvaroh

> А может лучше gtk_container_remove (...) который сделает unref сам?

Это одно и тоже. Вот выдержка из документации к gtk_container_remove:
If you don't want to use widget again it's usually more efficient to simply destroy it directly using gtk_widget_destroy() since this will remove it from the container and help break any circular reference count cycles.

KonishchevDmitry
() автор топика
Ответ на: комментарий от Bohtvaroh

> Сделай gtk_container_remove и чайлд сам подохнет.

Все дело в том, что чайлд как раз подыхает. Просто после его смерти его пытается использовать кто-то другой, о чем свидетельствует Segmentation fault.

KonishchevDmitry
() автор топика
Ответ на: комментарий от KonishchevDmitry

> Это одно и тоже.

Это вообще-то разные вещи. gtk_container_remove () вызовет (в случае GtkBox) gtk_box_remove () , который не только делает -- для ref_count, но и удаляет виджет из списка детей.

Bohtvaroh ★★★★
()
Ответ на: комментарий от KonishchevDmitry

> Все дело в том, что чайлд как раз подыхает.

Если исопльзовать gtk_container_remove, то сегфолт присутствует? (мало инфы даёшь, телепатим). ;-)

Bohtvaroh ★★★★
()
Ответ на: комментарий от Bohtvaroh

#include <gtk/gtk.h>

GtkWidget *vbox;                                                                                                                                                                                                 

void
change_callback (GtkTreeSelection *treeselection, GtkWidget *treeview)
{
    gtk_container_remove (GTK_CONTAINER (vbox), treeview);
}

int
main (int argc, char *argv[])
{
    GtkWidget *window, *treeview;
    GtkTreeIter iter;
    GtkTreeModel *items_model;
    GtkCellRenderer *renderer;

    gtk_init(&argc, &argv);

    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    vbox = gtk_vbox_new(FALSE, 5);
    gtk_container_add(GTK_CONTAINER(window), vbox);

    items_model = GTK_TREE_MODEL(gtk_list_store_new(1, G_TYPE_INT));
    gtk_list_store_append(GTK_LIST_STORE(items_model), &iter);
    gtk_list_store_set(GTK_LIST_STORE(items_model), &iter, 0, 1, -1);

    treeview = gtk_tree_view_new_with_model(items_model);
    g_object_unref (items_model);
    renderer = gtk_cell_renderer_combo_new();
    gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(treeview), -1, "Name", renderer, "text", 0, NULL);
    g_signal_connect(G_OBJECT(gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview)))

                     , "changed", G_CALLBACK(change_callback), treeview);
    gtk_box_pack_start(GTK_BOX(vbox), treeview, FALSE, FALSE, 0);

    gtk_widget_show_all (window);

    gtk_main();
    return 0;
}

Bohtvaroh ★★★★
()

Что интересно, в GDB (с дебаг-версиями glib и gtk+) change_callback срабатывает сразу при запуске. При этом gtk_widget_destroy отрабатывает нормально и отображается пустое окошко.

Bohtvaroh ★★★★
()
Ответ на: комментарий от Bohtvaroh

Bohtvaroh, хм, и правда работает, извините, что заставил вас проверять.

Но почему? :) Ведь в документации ясно сказано, что это одно и тоже. И у меня, честно говоря, все-таки возникают сомнения относительно того, действительно ли TreeView удалился, т. к. после gtk_container_remove только что удаленный treeview без проблем упаковывается в контейнер с помощью gtk_box_pack_start и отображается нормально - GTK даже не выводит никаких предупреждений на консоль.

KonishchevDmitry
() автор топика
Ответ на: комментарий от KonishchevDmitry

> И у меня, честно говоря, все-таки возникают сомнения относительно того, действительно ли TreeView удалился

У меня тоже: после удаления из контейнера ref_count дерева становится 5 (был 7). Я бы переписал в строчку что делает gtk_widget_destroy и что gtk_container_remove и сравнил что к чему.

Bohtvaroh ★★★★
()
Ответ на: комментарий от anonymous

Извиняюсь, тред не весь прочёл.

anonymous
()

GtkTreeSelection - это не GtkWidget совсем

GObject
+----GtkTreeSelection

тч g_object_unref() а вообще: действительно его нужно уничтожать?

no1sm ★★
()
Ответ на: комментарий от Bohtvaroh

> У меня тоже: после удаления из контейнера ref_count дерева становится 5 (был 7). Я бы переписал в строчку что делает gtk_widget_destroy и что gtk_container_remove и сравнил что к чему.

gtk_container_remove: 7->5
gtk_widget_destroy: 7->3

Но разве это что-нибудь дает? Откуда я знаю, кто держит эти 3 или 5 ссылок? Может быть одна из них как раз относится к GtkTreeSelection.

Дело в том, что эта ошибка возникает именно в обработчике "changed" GtkTreeSelection - в остальных все работает нормально (к примеру по щелчку на кнопке). Поэтому мне кажется, что это происходит потому, что функция, которая вызывает мой обработчик, после его вызова производит какие-то действия с объектом GtkTreeSelection, который я в своем обработчике удаляю (GtkTreeSelection - это объект, который содержится в каждом TreeView и удаляется вместе с ним).

Если понаблюдать за поведением TreeView, то можно заметить, что при клике мышкой по строке TreeView сначала происходит вызов моего обработчика, а уже потом производится графическое выделение строки, по которой я щелкнул, т. е. как раз, скорее всего, после вызова моего обработчика GTK обращается к GtkTreeSelection для того, чтобы определить, какие строки надо нарисовать как выделенные. Вот тут-то и происходит ошибка.

Единственное, что мне приходит в голову - это создать отдельный поток, которому я буду скармливать TreeView, которые мне необходимо уничтожить, и он уже будет после обработки всех сигналов их уничтожать, но это же такой костыль. =) Как сделать по-другому пока что ума не приложу.



> GtkTreeSelection - это не GtkWidget совсем, тч g_object_unref() а вообще: действительно его нужно уничтожать?

Я удаляю не GtkTreeSelection, а GtkTreeView. Просто в каждом GtkTreeView находится GtkTreeSelection, и он удаляется вместе с GtkTreeView. Вы просто не внимательно прочитали мое сообщение.

KonishchevDmitry
() автор топика
Ответ на: комментарий от KonishchevDmitry

Если не хочешь долго копаться сам - спроси у gtk-app-devel-list.

Bohtvaroh ★★★★
()

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

Ещё, как вариант, делай модальным. Из обработчика давай сигнал на выход из окна, и пущай родитель его портит, как положено.

fa652897
()
Ответ на: комментарий от ShprotX

Большое спасибо всем за помощь. Думаю вполне можно остановиться на gtk_widget_hide + g_idle_add.

KonishchevDmitry
() автор топика
Ответ на: комментарий от KonishchevDmitry

>> GtkTreeSelection - это не GtkWidget совсем, тч g_object_unref() а вообще: действительно его нужно уничтожать?

> Я удаляю не GtkTreeSelection, а GtkTreeView. Просто в каждом GtkTreeView находится GtkTreeSelection, и он удаляется вместе с GtkTreeView. Вы просто не внимательно прочитали мое сообщение.

Да действительно, не подумал, что treeview объявлен глобально.

no1sm ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.