You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

示例代码中GTK3调用ClosureNotify函数,GTK4为何不调用?

GTK3与GTK4绘图回调销毁通知行为差异的原因

运行以下示例代码及Makefile后,GTK3版本程序会输出“drop!”(即调用ctx_destroy_notify函数),而GTK4版本程序无该输出。即使将窗口显示并关闭的代码替换为g_object_run_dispose(G_OBJECT(window)),结果仍一致。两者行为不同的核心原因如下:


示例C代码

#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 loop\n");
  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

行为差异原因

  1. GTK3的信号连接机制
    在GTK3中,g_signal_connect_data会将销毁函数与信号绑定:当窗口关闭后,容器会销毁子部件drawing_area,部件销毁时会断开所有关联的信号连接,此时触发ctx_destroy_notify,所以会输出“drop!”。

  2. GTK4的绘图回调管理逻辑
    GTK4的gtk_drawing_area_set_draw_func对销毁函数的触发时机有不同的规则:如果绘图区域从未完成过绘制(比如窗口刚显示就被关闭,还没进入绘制流程),GTK4不会将回调的生命周期与部件销毁强绑定,或者会延迟清理逻辑。而程序在主循环结束后直接退出,导致销毁函数没有执行的机会。

如果在gtk_window_close前添加g_usleep(100000)让主循环运行一小段时间,让绘图区域完成至少一次绘制,GTK4版本会触发ctx_destroy_notify输出“drop!”——因为此时回调已被激活,关联数据的生命周期会和部件绑定。

内容的提问来源于stack exchange,提问作者Donald

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 09:35:15