GSource
Αυτό το κείμενο είναι ένα μάθημα για την δημιουργία ενός προσαρμοσμένου 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 (¶m_value, G_TYPE_POINTER);
g_value_set_pointer (¶m_value, message);
g_closure_invoke (closure, &result_value, 1, ¶m_value, NULL);
retval = g_value_get_boolean (&result_value);
g_value_unset (¶m_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.