making my C more like Python

Asynchronous programming in C can be such a pain. You’re always creating little structs in order to pass around user_data. So often I find myself wishing it were like Python, and I could just create an anonymous tuple.

Then I had this crazy idea.

DBus is always passing around tuples. dbus-glib represents these as GValueArrays. They’re so ubiquitous that telepathy-glib added utility functions to deal with them: tp_value_array_build() and tp_value_array_unpack().

We can use them like this:

my_request_async (obj, my_callback,
  tp_value_array_build (2,
    G_TYPE_POINTER, my_pointer,
    G_TYPE_UINT, my_uint,
    G_TYPE_INVALID));

With the callback:

static void
my_callback (GObject *obj,
    GAsyncResult *result,
    gpointer user_data)
{
  gpointer my_pointer;
  guint my_uint;
 
  tp_value_array_unpack (user_data, 2,
      &my_pointer,
      &my_uint);
 
  ...
 
finally:
  g_value_array_free (user_data);
}

In my specific use case, I was making requests for a property, and then needed the generic callback to know which property the request was for. I could have copied and pasted the request multiple times, or get increasingly meta:

#define MY_REQUEST_ASYNC(prop) \
  my_request_async (obj, prop, \
      my_callback, tp_value_array_build (2, \
        G_TYPE_POINTER, my_pointer, \
        G_TYPE_UINT, prop, \
        G_TYPE_INVALID));
 
  MY_REQUEST_ASYNC (PROP_1);
  MY_REQUEST_ASYNC (PROP_2);
 
#undef MY_REQUEST_ASYNC

Where this gets really useful is when you’re having to copy or reference your members: strings, objects, hash tables, etc.. You no longer need to write new() and free() functions for each structure. GValue already knows how to take care of it.

my_request_async (obj, my_callback,
  tp_value_array_build (3,
    G_TYPE_OBJECT, my_obj,
    G_TYPE_STRING, "escher",
    G_TYPE_BOOLEAN, TRUE,
    G_TYPE_INVALID));

Everything will be released when you call g_value_array_free().

You could go further. For optional numbers of arguments you could use an a{sv} map (telepathy-glib also has utility methods to manipulate these, i.e. tp_asv_new()).

Unfortunately not everyone has telepathy-glib in their stack. I bet you could also achieve the same result using GVariant with g_variant_new(), g_variant_get() and g_variant_unref(). Store your pointers as type ‘t’ and remember to cast them to (guint64) for correct var-args alignment. Unfortunately GVariant can’t do your ref-counting.

Author: Danielle

Danielle is an Australian software engineer, computer scientist and feminist. She doesn't really work on GNOME any more (sadly). Opinions and writing are solely her own and so not represent her employer, the GNOME Foundation, or anyone else but herself.

9 thoughts on “making my C more like Python”

  1. Umm, while certainly a neat trick, is that #define actually any more readable and less typing than:

    struct _callback_data {
    gpointer userdata;
    guint prop;
    };

    What you really want are blocks.

  2. In my opinion the struct is certainly more readable, but so frequently I find myself investing a lot of energy adding structs that aren’t very generic, which have their own new() and free() functions (to make sure I get the memory management correct), and in the end each one takes up 25+ lines. This really sucks for a one-use struct.

    Think of a more complex case where you need to do memory allocation or reference counting and suddenly it seems appealing to let GValue take care of it for you. It knows how to take copies/refs of everything, and how to release them without you having to write your own new() or free() function.

  3. Or you use C anonymous aggregates just for the heck of it. Didn’t know that worked myself, and certainly causes a few warnings, but you get the idea:

    #include
    #include

    static void
    f3 (struct { void *ptr; int i; } *param)
    {
    printf (“%p %i\n”, param->ptr, param->i);
    }

    int
    main (int argc,
    char **argv)
    {
    f3 ( & (struct { void *ptr; int i;}) { 0xdeadbeef, 1 });

    return EXIT_SUCCESS;
    }

  4. This structure is being allocated on the stack, so it will be popped off when you reach the end of the function making the asynchronous request and your callback will be given a pointer into random stack memory. It works here because you’re not leaving the outer stack level before the callee returns. You need to allocate your structure on the heap so that it still exists when the callback is called.

  5. Like we had any type safety anyway. The entire of GLib is built on not having type safety.

    I totally agree that it can break in all kinds of subtle, hard to detect ways, a lot like Python tuples really.

  6. While making C code more wei^H^H^H pythonic …

    #include
    #include
    #include
    #include

    #define G_SIGNAL_HANDLE(object_, signal_, data_, return_, func_, args_, impl_) \
    return_ func_ args_ impl_; \
    g_signal_connect (object_, signal_, G_CALLBACK (func_), data_);

    int
    main (int argc,
    char **argv)
    {
    GtkWidget *window;
    GtkWidget *button;

    gtk_init (&argc, &argv);

    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    g_signal_connect (window, “delete-event”, G_CALLBACK (gtk_main_quit), NULL);

    button = gtk_button_new_with_label (“Click me”);
    gtk_container_add (GTK_CONTAINER (window), button);

    G_SIGNAL_HANDLE (button, “clicked”, “Function is”,
    void, clicked, (GtkButton *button,
    char const *message),
    {
    printf (“%s: %s\n”, message, __FUNCTION__);
    });

    gtk_widget_show_all (window);
    gtk_main ();

    return EXIT_SUCCESS;
    }

  7. “You need to allocate your structure on the heap so that it still exists when the callback is called.”

    Hence my recommendation for using Blocks 🙂
    If only we had Blocks on a free software system

Leave a Reply

Your email address will not be published. Required fields are marked *

Creative Commons Attribution-ShareAlike 2.5 Australia
This work by Danielle Madeley is licensed under a Creative Commons Attribution-ShareAlike 2.5 Australia.