为什么 gtk3 在示例代码中调用 ClosureNotify 函数,而 gkt4 没有?



当这个示例程序

#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->绘制回调->ctxdrawing_area

参考ctx->正确地创建了drawing_areag_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"行动
  • 使用弱引用

最新更新