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.

# Πηγή μιας ουράς μηνύματος

Για παράδειγμα, η πηγή μιας ουράς μηνύματος θα χρησιμοποιηθεί για να σταλθεί η επανάκληση της κάθε φορά που ένα μήνυμα θα μπει στην ουρά της πηγής (συνήθως από άλλο νήμα).

Αυτός ο τύπος πηγής είναι χρήσιμος για την αποτελεσματική μεταφορά μεγάλου αριθμού μηνυμάτων. Ένας εναλλακτικός τρόπος είναι η μεταφορά κάθε μηνύματος ως ξεχωριστό `GSource` χρησιμοποιώντας το `g_source_attach()`. Για μεγάλο αριθμό μηνυμάτων, αυτό σημαίνει πως θα υπάρχουν πολλές λειτουργίες κατανομών και ελευθέρωσης των `GSource`.

## Δομή

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

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

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

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

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

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

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

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

Η συνάρτηση αποστολής είναι το πιο πολύπλοκο κομμάτι. Πρέπει να πάρει ένα μήνυμα από την ουρά και να το περάσει στην συνάρτηση επανάκλησης του `GSource`. Δεν είναι απαραίτητο να υπάρχουν μηνύματα στην ουρά: ακόμα και αν η συνάρτηση προετοιμασίας επιστρέψει αληθές, μια άλλη πηγή μπορεί να έχει δεχτεί το τελικό μήνυμα από την ουρά. Επίσης, αν δεν είχε οριστεί μια συνάρτηση επανάκλησης για το `GSource` (το οποίο είναι αποδεκτό), το μήνυμα πρέπει να καταστραφεί.

Αν έχει οριστεί ένα μήνυμα και μια επανάκληση, η επανάκληση μπορεί να καλεστεί από το μήνυμα και να επιστρέψει μια τιμή, όπως η τιμή που επιστρέφεται από την συνάρτηση αποστολής. Αυτό ορίζεται ως `FALSE` για την καταστροφή του `GSource` και `TRUE` για να παραμείνει ως έχει, όπως και το `GSourceFunc` — αυτά παραμένουν τα ίδια για όλες τις υλοποιήσεις μιας συνάρτησης αποστολής.

/* 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);

Ωστόσο, το `GSource` διαθέτει μια στρώση για τη ήψη της επανάκλησης, ως `g_source_set_callback_indirect()`. Αυτό επιτρέπει το GObject να ορίσει ένα `GClosure` ως επανάκληση για την πηγή, επιτρέποντας τις πηγές να καταστρέφονται αυτόματα όταν οριστοποιηθεί ένα αντικείμενο — μια χαλαρή αναφορά, σε αντίθεση με την ισχυρή αναφορά παραπάνω:

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

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

g_source_set_dummy_callback (source);

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

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

Το παράδειγμα του κατασκευαστή παρουσιάζει τη χρήση μιας θυγατρικής πηγής για την υποστήριξη της λειτουργίας ακύρωσης. Αν ακυρωθεί το `GCancellable`, η επανάκλαση της εφαρμογής θα σταλθεί και θα ελέγξει για την ακύρωση. (Ο κώδικας της εφαρμογής θα πρέπει να δημιουργήσει έναν δείκτη στο `GCancellable` που θα είναι διαθέσιμο στην επανάκληση ως ένα πεδίο για τα δεδομένα του χρήστη στο `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 και παρουσιάζει μια πιο πολύπλοκη συνάρτηση προετοιμασίας.

Another example is a custom source to interface GnuTLS with GLib in its `GTlsConnection` implementation. `GTlsConnectionGnutlsSource` synchronizes the main thread and a TLS worker thread which performs the blocking TLS operations.