Philip Withnall
philip.withnall@collabora.co.uk 2015
Μάθημα για την εγγραφή μιας προσαρμοσμένης υλοποίησης `GSource`
Ελληνική μεταφραστική ομάδα GNOME
team@gnome.gr 2012-2015
Δημήτρης Σπίγγος
dmtrs32@gmail.com 2012, 2013
Μαρία Θουκιδίδου
marablack3@gmail.com 2014
Θάνος Τρυφωνίδης
tomtryf@gmail.com 2014, 2015

# Προσαρμοσμένα GSources

#

        Περίληψη

        Αυτό το κείμενο είναι ένα μάθημα για την δημιουργία ενός προσαρμοσμένου GSource. Αν θέλετε να δείτε περισσότερη τεκμηρίωση, επισκεφθείτε την αναφορά του GLib API.

# Τι είναι `GSource`;

Το `GSource` είναι ένα γεγονός με μια συσχετισμένη συνάρτηση επανάκλησης η οποία καλείται όταν λαμβάνεται το γεγονός. Το γεγονός αυτό μπορεί να είναι ένα χρονικό όριο ή δεδομένα που λαμβάνονται από μια υποδοχή.

Το GLib περιλαμβάνει διάφορους τύπους `GSource`, αλλά επιτρέπει επίσης της εφαρμογές να ορίσουν τα δικά τους, επιτρέποντας προσαρμοσμένα συμβάντα να ενσωματωθούν στον κύριο βρόγχο του προγράμματος.

Η δομή του `GSource` και των εικονικών συναρτήσεων περιγράφονται λεπτομερώς στην αναφορά του GLib API.

# A Message Queue Source

As a running example, a message queue source will be used which dispatches its callback whenever a message is enqueued to a queue internal to the source (potentially from another thread).

This type of source is useful for efficiently transferring large numbers of messages between main contexts. The alternative is transferring each message as a separate idle `GSource` using `g_source_attach()`. For large numbers of messages, this means a lot of allocations and frees of `GSource`s.

## Δομή

Αρχικά, πρέπει να οριστεί μια δομή για τις ανάγκες του κώδικα. Αυτή η δομή πρέπει να περιέχει ένα `GSource` ως γονικό, ακολουθόμενο από ιδιωτικά πεδία: την ουρά και μια συνάρτηση για να καλεστεί κάθε μήνυμα όταν τελειώσει.

typedef struct { GSource parent; GAsyncQueue *queue; /* owned */ GDestroyNotify destroy_message; } MessageQueueSource;

## Συνάρτηση προετοιμασίας

Στη συνέχεια, πρέπει να οριστεί η συνάρτηση προετοιμασίας. Αυτή καθορίζει αν ο κώδικας είναι έτοιμος να σταλθεί. Καθώς αυτός ο κώδικας χρησιμοποιεί μια ουρά μνήμης, η ενέργεια μπορεί να καθοριστεί ελέγχοντας το μήκος της ουράς: αν υπάρχουν στοιχεία στην ουρά, τότε ο κώδικας μπορεί να σταλθεί για να τα διαχειριστεί.

return (g_async_queue_length (message_queue_source->queue) > 0);

## Συνάρτηση ελέγχου

Καθώς αυτή η πηγή δεν έχει καθόλου περιγραφείς αρχείων, οι συναρτήσεις προετοιμασίας και ελέγχου εκτελούν την ίδια δουλειά, έτσι δεν είναι αναγκαία μια συνάρτηση ελέγχου. Ορίζοντας το πεδίο σε `NULL` στο `GSourceFuncs` παρακάμπτεται η συνάρτηση ελέγχου για αυτόν τον τύπο πηγής.

## Συνάρτηση αποστολής

For this source, the dispatch function is where the complexity lies. It needs to dequeue a message from the queue, then pass that message to the `GSource`’s callback function. No messages may be queued: even through the prepare function returned true, another source wrapping the same queue may have been dispatched in the mean time and taken the final message from the queue. Further, if no callback has been set for the `GSource` (which is allowed), the message must be destroyed and silently dropped.

If both a message and callback are set, the callback can be invoked on the message and its return value propagated as the return value of the dispatch function. This is `FALSE` to destroy the `GSource` and `TRUE` to keep it alive, just as for `GSourceFunc` — these semantics are the same for all dispatch function implementations.

/* Pop a message off the queue. */ message = g_async_queue_try_pop (message_queue_source->queue);

/* If there was no message, bail. */ if (message == NULL) { /* Keep the source around to handle the next message. */ return TRUE; }

/* @func may be %NULL if no callback was specified. * If so, drop the message. */ if (func == NULL) { if (message_queue_source->destroy_message != NULL) { message_queue_source->destroy_message (message); }

/* Keep the source around to consume the next message. */ return TRUE; }

return func (message, user_data);

## Συναρτήσεις επανάκλησης

Η επανάκληση από το `GSource` δεν είναι αναγκαίο να είναι τύπου `GSourceFunc`. Μπορεί να είναι οποιουδήποτε τύπου συνάρτηση στην πηγή της συνάρτησης επανάκλησης, εφόσον αυτός ο τύπος είναι επαρκώς τεκμηριωμένος.

Συνήθως, το `g_source_set_callback()` χρησιμοποιείται για να οριστεί η συνάρτηση επανάκλησης για ένα στιγμιότυπο πηγής. Με το `GDestroyNotify` του, μια ισχυρή αναφορά μπορεί να δημιουργηθεί για να κρατήσει ζωντανό το αντικείμενο καθώς η πηγή είναι ακόμα ενεργή:

g_source_set_callback (source, callback_func, g_object_ref (object_to_strong_ref), (GDestroyNotify) g_object_unref);

However, `GSource` has a layer of indirection for retrieving this callback, exposed as `g_source_set_callback_indirect()`. This allows GObject to set a `GClosure` as the callback for a source, which allows for sources which are automatically destroyed when an object is finalized — a weak reference, in contrast to the strong reference above:

g_source_set_closure (source, g_cclosure_new_object (callback_func, object_to_weak_ref));

Επιτρέπει επίσης για μια γενική, εικονική επανάκληση, η οποία μπορεί να χρησιμοποιηθεί όταν μια πηγή χρειάζεται να τερματιστεί αλλά δεν απαιτείται να εκτελεστούν ενέργειες στην επανάκληση της:

g_source_set_dummy_callback (source);

## Κατασκευαστής

Finally, the `GSourceFuncs` definition of the `GSource` can be written, alongside a construction function. It is typical practice to expose new source types simply as `GSource`s, not as the subtype structure; so the constructor returns a `GSource*`.

The example constructor here also demonstrates use of a child source to support cancellation conveniently. If the `GCancellable` is cancelled, the application’s callback will be dispatched and can check for cancellation. (The application code will need to make a pointer to the `GCancellable` available to its callback, as a field of the callback’s user data set in `g_source_set_callback()`).

GSource * message_queue_source_new (GAsyncQueue *queue, GDestroyNotify destroy_message, GCancellable *cancellable) { GSource *source; /* alias of @message_queue_source */ MessageQueueSource *message_queue_source; /* alias of @source */

g_return_val_if_fail (queue != NULL, NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);

source = g_source_new (&message_queue_source_funcs, sizeof (MessageQueueSource)); message_queue_source = (MessageQueueSource *) source;

/* The caller can overwrite this name with something more useful later. */ g_source_set_name (source, "MessageQueueSource");

message_queue_source->queue = g_async_queue_ref (queue); message_queue_source->destroy_message = destroy_message;

/* Add a cancellable source. */ if (cancellable != NULL) { GSource *cancellable_source;

cancellable_source = g_cancellable_source_new (cancellable); g_source_set_dummy_callback (cancellable_source); g_source_add_child_source (source, cancellable_source); g_source_unref (cancellable_source); }

return source; }

# Πλήρες παράδειγμα

## Δείγμα πλήρους κώδικα

/** * MessageQueueSource: * * This is a #GSource which wraps a #GAsyncQueue and is dispatched whenever a * message can be pulled off the queue. Messages can be enqueued from any * thread. * * The callbacks dispatched by a #MessageQueueSource have type * #MessageQueueSourceFunc. * * #MessageQueueSource supports adding a #GCancellable child source which will * additionally dispatch if a provided #GCancellable is cancelled. */ typedef struct { GSource parent; GAsyncQueue *queue; /* owned */ GDestroyNotify destroy_message; } MessageQueueSource;

/** * MessageQueueSourceFunc: * @message: (transfer full) (nullable): message pulled off the queue * @user_data: user data provided to g_source_set_callback() * * Callback function type for #MessageQueueSource. */ typedef gboolean (*MessageQueueSourceFunc) (gpointer message, gpointer user_data);

static gboolean message_queue_source_prepare (GSource *source, gint *timeout_) { MessageQueueSource *message_queue_source = (MessageQueueSource *) source;

return (g_async_queue_length (message_queue_source->queue) > 0); }

static gboolean message_queue_source_dispatch (GSource *source, GSourceFunc callback, gpointer user_data) { MessageQueueSource *message_queue_source = (MessageQueueSource *) source; gpointer message; MessageQueueSourceFunc func = (MessageQueueSourceFunc) callback;

/* Pop a message off the queue. */ message = g_async_queue_try_pop (message_queue_source->queue);

/* If there was no message, bail. */ if (message == NULL) { /* Keep the source around to handle the next message. */ return TRUE; }

/* @func may be %NULL if no callback was specified. * If so, drop the message. */ if (func == NULL) { if (message_queue_source->destroy_message != NULL) { message_queue_source->destroy_message (message); }

/* Keep the source around to consume the next message. */ return TRUE; }

return func (message, user_data); }

static void message_queue_source_finalize (GSource *source) { MessageQueueSource *message_queue_source = (MessageQueueSource *) source;

g_async_queue_unref (message_queue_source->queue); }

static gboolean message_queue_source_closure_callback (gpointer message, gpointer user_data) { GClosure *closure = user_data; GValue param_value = G_VALUE_INIT; GValue result_value = G_VALUE_INIT; gboolean retval;

/* The invoked function is responsible for freeing @message. */ g_value_init (&result_value, G_TYPE_BOOLEAN); g_value_init (&param_value, G_TYPE_POINTER); g_value_set_pointer (&param_value, message);

g_closure_invoke (closure, &result_value, 1, &param_value, NULL); retval = g_value_get_boolean (&result_value);

g_value_unset (&param_value); g_value_unset (&result_value);

return retval; }

static GSourceFuncs message_queue_source_funcs = { message_queue_source_prepare, NULL, /* check */ message_queue_source_dispatch, message_queue_source_finalize, (GSourceFunc) message_queue_source_closure_callback, NULL, };

/** * message_queue_source_new: * @queue: the queue to check * @destroy_message: (nullable): function to free a message, or %NULL * @cancellable: (nullable): a #GCancellable, or %NULL * * Create a new #MessageQueueSource, a type of #GSource which dispatches for * each message queued to it. * * If a callback function of type #MessageQueueSourceFunc is connected to the * returned #GSource using g_source_set_callback(), it will be invoked for each * message, with the message passed as its first argument. It is responsible for * freeing the message. If no callback is set, messages are automatically freed * as they are queued. * * Returns: (transfer full): a new #MessageQueueSource */ GSource * message_queue_source_new (GAsyncQueue *queue, GDestroyNotify destroy_message, GCancellable *cancellable) { GSource *source; /* alias of @message_queue_source */ MessageQueueSource *message_queue_source; /* alias of @source */

g_return_val_if_fail (queue != NULL, NULL); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);

source = g_source_new (&message_queue_source_funcs, sizeof (MessageQueueSource)); message_queue_source = (MessageQueueSource *) source;

/* The caller can overwrite this name with something more useful later. */ g_source_set_name (source, "MessageQueueSource");

message_queue_source->queue = g_async_queue_ref (queue); message_queue_source->destroy_message = destroy_message;

/* Add a cancellable source. */ if (cancellable != NULL) { GSource *cancellable_source;

cancellable_source = g_cancellable_source_new (cancellable); g_source_set_dummy_callback (cancellable_source); g_source_add_child_source (source, cancellable_source); g_source_unref (cancellable_source); }

return source; }

# Περισσότερα παραδείγματα

Ο κώδικας μπορεί να είναι πιο πολύπλοκος από τα παραπάνω παραδείγματα. Στο libnice, ένα προσαρμοσμένο `GSource` χρειάζεται για να λαμβάνει μια ομάδα υποδοχών που αλλάζει δυναμικά. Η υλοποίηση του γίνεται με το `ComponentSource` στο αρχείο component.c και παρουσιάζει μια πιο πολύπλοκη συνάρτηση προετοιμασίας.

Ένα άλλο παράδειγμα είναι ένας προσαρμοσμένος κώδικας για την επικοινωνία του GnuTLS με το GLib και την υλοποίηση `GTlsConnection` του. Το `GTlsConnectionGnutlsSource` συγχρονίζει το κύριο νήμα με ένα νήμα TLS το οποίο εκτελεί τις εργασίες του TLS.