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.

Τι είναι <code>GSource</code>;

Το 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 και παρουσιάζει μια πιο πολύπλοκη συνάρτηση προετοιμασίας.

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