0

When this example program

#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);
}

is run with this 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`

The output is:

./test3
drop!
end of main loop
./test4
end of main loop

So ctx_destroy_notify is called with gtk3 but not with gtk4. Note, that gtk_widget_show (window); gtk_window_close(GTK_WINDOW(window)); can be replaced with g_object_run_dispose(G_OBJECT(window)); with the same result.

Why do gtk3 and gtk4 behave differently here?

[ to be ignored: stackoverflow wants a little bit more text, so that the text to code ratio is acceptable to its heuristic... ]

Edit 31.12.2022: Add first few lines of source, which were missing because of copy-and-paste mistake.

Donald
  • 19
  • 3

1 Answers1

0

Okay, as nobody jumped on this, I try to answer that myself.

The code, obviously, contains a circular reference: drawing_area -> draw callback -> ctx -> drawing_area

The reference ctx -> drawing_area is correctly created with  g_object_ref(), the other references are implied by gtk.

The function ctx_destroy_notify() in the code would break the reference cycle with g_object_unref() if it gets called.

It is true, that gtk3 calls ctx_destroy_notify() when the application window is closed. This is a result of the fact that gtk3 immediately disposes the child widgets when a window is destroyed. But this is just an implementation detail.

There were substantial changes in this area between gtk3 and gtk4. See "Life-cycle handling" at https://docs.gtk.org/gtk4/migrating-3to4.html.

Here is a related discussion: https://gitlab.gnome.org/GNOME/gtk/-/issues/3243

In short: While the destroy_data notify of signal_connect_data() can be used to clean up the user data of the callback when the object is destroyed, it can not reliably be used to break a reference cycle which prevents the object from being destroyed in the first place.

Possible approaches to clean up the reference cycle would be:

  • subclass one of the widgets and make the ctx data part of it
  • For gtk4: Use a "win.close" action
  • Use weak references in ctx
Donald
  • 19
  • 3