当这个示例程序
#include <gtk/gtk.h>
static void draw_func(
GtkDrawingArea* drawing_area,
cairo_t *cr,
#ifdef GTK3
#else
int width,
int height,
#endif
gpointer user_data
) {
printf("draw!n");
}
struct Ctx {
char *text;
GtkWidget *drawing_area;
};
static void ctx_destroy_notify(
gpointer data
#ifdef GTK3
, GClosure* closure
#endif
) {
struct Ctx *ctx = data;
printf("drop!n");
free(ctx->text);
g_object_unref(ctx->drawing_area);
free(ctx);
}
static void
activate (GtkApplication* app,
gpointer user_data)
{
GtkWidget *drawing_area = gtk_drawing_area_new();
GtkWidget *window = gtk_application_window_new (app);
struct Ctx *ctx = malloc(sizeof(struct Ctx));
ctx->text = strdup("Hallo");
ctx->drawing_area = g_object_ref(drawing_area);
#ifdef GTK3
g_signal_connect_data(G_OBJECT(drawing_area), "draw", G_CALLBACK(draw_func), ctx, ctx_destroy_notify, G_CONNECT_AFTER);
#else
gtk_drawing_area_set_draw_func( GTK_DRAWING_AREA(drawing_area), draw_func, ctx, ctx_destroy_notify);
#endif
#ifdef GTK3
gtk_container_add (GTK_CONTAINER (window), drawing_area);
#else
gtk_window_set_child (GTK_WINDOW(window), drawing_area);
#endif
#ifdef GTK3
gtk_widget_show_all(window);
#else
gtk_widget_show(window);
#endif
gtk_window_close(GTK_WINDOW(window));
}
int
main (int argc,
char **argv)
{
GtkApplication * app = gtk_application_new ("org.example.test", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK (activate), NULL);
g_application_run (G_APPLICATION (app), argc, argv);
printf("end of main loopn");
g_object_unref (app);
}
在Makefile下运行:
.PHONY: test
test: test3 test4
./test3
./test4
test3: test.c
gcc -g -O0 -DGTK3 `pkg-config --cflags gtk+-3.0` -o test3 test.c `pkg-config --libs gtk+-3.0`
test4: test.c
gcc -g -O0 -DGTK4 `pkg-config --cflags gtk4` -o test4 test.c `pkg-config --libs gtk4`
输出为:
./test3
drop!
end of main loop
./test4
end of main loop
所以ctx_destroy_notify
被gtk3调用,而不是gtk4。注意,gtk_widget_show (window); gtk_window_close(GTK_WINDOW(window));
可以用g_object_run_dispose(G_OBJECT(window));
代替,结果相同。
为什么gtk3和gtk4在这里的行为不同?
[被忽略:stackoverflow需要更多的文本,以便文本与代码的比例可以接受它的启发式…)
编辑31.12.2022:添加源代码的前几行,因为复制粘贴错误而丢失。
好吧,既然没人回答这个问题,我就自己来回答吧。
代码显然包含了一个循环引用:drawing_area
->绘制回调->ctx
→drawing_area
参考ctx
->正确地创建了drawing_area
g_object_ref()
,其他引用由gtk暗示。
代码中的ctx_destroy_notify()
函数将破坏引用循环g_object_unref()
如果它被调用。
确实,gtk3在应用程序窗口关闭时调用ctx_destroy_notify()
。这是因为当窗口被销毁时,gtk3会立即处置子部件。但这只是一个实现细节。
gtk3和gtk4在这一区域有很大的变化。参见"生命周期处理";在https://docs.gtk.org/gtk4/migrating-3to4.html .
下面是一个相关的讨论:https://gitlab.gnome.org/GNOME/gtk/-/issues/3243
简而言之:虽然signal_connect_data()
的destroy_data
通知可以用于在对象被销毁时清理回调的用户数据,但它不能可靠地用于打破引用循环,从而阻止对象在第一个位置被销毁。
清理引用循环的可能方法有:
- 子类化一个小部件并使ctx数据成为它的一部分
- 对于gtk4:使用"win.close"行动 使用弱引用