GSource
.Tento článek je výukou ve vytváření vlastních GSource
. Ohledně referenční dokumentace se podívejte do referenční příručky k API knihovny GLib.
GSource
?GSource
je očekávaná událost s přiřazenou funkcí zpětného volání, která bude vyvolána, když je událost přijata. Událostí může být například vypršení času nebo přijetí dat na soketu.
GLib obsahuje různé typy GSource
, ale umožňuje také aplikacím definovat si své vlastní, díky čemuž můžete začlenit vlastní události do hlavní smyčky.
Struktura GSource
a jeho virtuální funkce jsou podrobně zdokumentovány v referenční příručce k API GLib.
Jak funkční příklad je použit zdroj fronty zpráv, který posílá svá zpětná volání, kdykoliv je do interní fronty ve zdroji zařazena zpráva (teoreticky z jiného vlákna).
Tento typ zdroje je vhodný pro efektivní přenosy velkého množství zpráv mezi hlavními kontexty. Alternativou je přenášet každou zprávu jako samostatný GSource
pomocí g_source_attach()
. Pro opravdu velké množství zpráv to znamená velký počet přidělování a uvolňování zdrojů GSource
.
Nejprve potřebujeme deklarovat strukturu pro zdroj. Ta musí obsahovat GSource
jako svého rodiče, následovaného soukromými poli pro zdroj: frontou queue
a funkcí destroy_message
, která se volá, aby po dokončení práce se zprávou, zprávu uvolnila z paměti.
typedef struct {
GSource parent;
GAsyncQueue *queue; /* vlastněno */
GDestroyNotify destroy_message;
} MessageQueueSource;
Následně připravíme funkci, které musí být pro zdroj definována. Určuje, jestli je zdroj připraven posílat. Protože tento zdroj používá frontu v paměti, můžeme to určit pomocí kontroly délky fronty. Jestliže jsou ve frontě nějaké prvky, zdroj je může posílat, aby je obsloužil.
return (g_async_queue_length (message_queue_source->queue) > 0);
Protože tento zdroj nemá žádné popisovače souborů, funkce prepare a check dělají v podstatě tu samou práci, takže funkce check není zapotřebí. Nastavením pole na NULL
v GSourceFuncs
funkce check pro tento typ zdroje obejdeme.
U tohoto zdroje je funkce dispatch
místo, kde se nachází to nejsložitější. Musí vyřadit zprávu z fronty, pak tuto zprávu předat do funkce zpětného volání z GSource
. Ve frontě nemusí být žádná zpráva: i přesto, že funkce prepare
vrátila true
, jiný zdroj postavený nad touto frontou mohl být mezitím obeslán a zprávu nakonec z fronty odebrat. Navíc, jestliže nebylo nastaveno žádné zpětné volání pro GSource
(což je dovolené), musí být zpráva zlikvidována a tiše zahozena.
Když je nastavena zpráva a zpětné volání, může být zpětné volání vyvoláno zprávou a jeho návratová hodnota je propagována jako návratová hodnota funkce dispatch
. Je to FALSE
pro zlikvidování GSource
a TRUE
pro jeho zachování, stejně jako u GSourceFunc
— tyto sémantiky jsou stejné u všech implementací funkce dispatch
.
/* Vytáhne zprávu z fronty */
message = g_async_queue_try_pop (message_queue_source->queue);
/* Když žádná zpráva není, opustíme to */
if (message == NULL)
{
/* Udrží zdroj pro zpracování následující zprávy */
return TRUE;
}
/* @func může být %NULL, pokud nění určeno žádné zpětné volání
* Jestli tomu tak je, zahodí zprávu */
if (func == NULL)
{
if (message_queue_source->destroy_message != NULL)
{
message_queue_source->destroy_message (message);
}
/* Udrží zdroj pro spotřebování následující zprávy */
return TRUE;
}
return func (message, user_data);
Zpětné volání z GSource
nemusí mít typ GSourceFunc
. Může to být jakýkoliv typ funkce, který je volán ve funkci dispatch
, pokud je daný typ dostatečně zdokumentován.
Normálně se k nastavení funkce zpětného volání pro instanci zdroje používá g_source_set_callback()
. Spolu s GDestroyNotify
může být udržován silný odkaz kvůli zachování objektu při životě, dokud je existuje zdroj:
g_source_set_callback (source, callback_func,
g_object_ref (object_to_strong_ref),
(GDestroyNotify) g_object_unref);
Nicméně, GSource
má nepřímou vrstvu pro získání tohoto zpětného volání, vystavenou jako g_source_set_callback_indirect()
. Ta umožňuje u GObject
nastavit GClosure
jako zpětné volání pro zdroj, která umožňuje zdroje, které jsou automaticky likvidovány, když je objekt finalizován – slabý odkaz, v kontrastu k silnému odkazu výše:
g_source_set_closure (source,
g_cclosure_new_object (callback_func,
object_to_weak_ref));
Obecně je možné i uzavřené „fiktivní“ zpětné volání, které může být použito, když je potřeba, aby zdroj existoval, ale není potřeba provést žádnou činnost při jeho zpětném volání:
g_source_set_dummy_callback (source);
Konečně může být napsána definice GSourceFuncs
pro GSource
, spolu s funkcí konstruktoru. V praxi je typické vystavit nové typy zdrojů jednoduše jako GSource
, ne jako podtyp struktury, takže konstruktor vrací GSource*
.
Konstruktor v tomto příkladu rovněž předvádí použití podřízeného zdroje pro podporu pohodlného zrušení. Když je GCancellable
zrušen, bude obesláno zpětné volání aplikace a může ověřit zrušení. (Kód aplikace bude muset vytvořit ukazatel na GCancellable
dostupný jeho zpětnému volání, v podobě parametru s uživatelskými daty zpětného volání v g_source_set_callback()
.)
GSource *
message_queue_source_new (GAsyncQueue *queue,
GDestroyNotify destroy_message,
GCancellable *cancellable)
{
GSource *source; /* přezdívka pro @message_queue_source */
MessageQueueSource *message_queue_source; /* přezdívka pro @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;
/* Volající může později přepsat tento název něčím vhodnějším. */
g_source_set_name (source, "MessageQueueSource");
message_queue_source->queue = g_async_queue_ref (queue);
message_queue_source->destroy_message = destroy_message;
/* Přidá zdroj, který lze zrušit. */
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:
*
* Jde o #GSource obalující #GAsyncQueue a je poslán kdykoliv je do fronty
* vložena zpráva. Zprávy mohou být vkládány z libovolného vlákna.
*
* Zpětná volání poslaná od #MessageQueueSource mají typ
* #MessageQueueSourceFunc.
*
* #MessageQueueSource podporuje přídávání synovských zdrojů #GCancellable,
* které budou poslány navíc, když je poskytující #GCancellable rušen.
*/
typedef struct {
GSource parent;
GAsyncQueue *queue; /* vlastník */
GDestroyNotify destroy_message;
} MessageQueueSource;
/**
* MessageQueueSourceFunc:
* @message: (úplný přenos) (může být null): zprávy vytažená z fronty
* @user_data: uživatelská data poskytnutá do g_source_set_callback()
*
* Typ funkce zpětného volání pro #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;
/* Vytáhne zprávu z fronty */
message = g_async_queue_try_pop (message_queue_source->queue);
/* Když zde není žádná zpráva, skončit */
if (message == NULL)
{
/* Zachová zdroj pro obsluhu následující zprávy */
return TRUE;
}
/* @func může být %NULL, když nebylo určeno žádné zpětné volání
* Pokud tomu tak je, zahodí zprávu */
if (func == NULL)
{
if (message_queue_source->destroy_message != NULL)
{
message_queue_source->destroy_message (message);
}
/* Zachová zdroj pro zpracování následující zprávy */
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;
/* Vyvolaná funkce je zodpovědná za uvolnění @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: fronta ke zkontrolování
* @destroy_message: (může být null): funkce pro uvolnění zprávy nebo %NULL
* @cancellable: (může být null): #GCancellable nebo %NULL
*
* Vytvoří nový #MessageQueueSource typu #GSource, který posílá při každé
* zprávě přidané do fronty
*
* Pokud je funkce zpětného volání typu #MessageQueueSourceFunc napojená na
* vrácený #GSource pomocí g_source_set_callback(), bude vyvolána pro každou
* zpráv, přičemž zpráva bude předána jako první argument. Zodpovídá za
* uvolnění zprávy. Pokud žádné zpětné volání není nastaveno, je zpráva
* automaticky uvolněna, stejně jako byla přidána.
*
* Vrací: (úplný přenos): nový #MessageQueueSource
*/
GSource *
message_queue_source_new (GAsyncQueue *queue,
GDestroyNotify destroy_message,
GCancellable *cancellable)
{
GSource *source; /* přezdívka pro @message_queue_source */
MessageQueueSource *message_queue_source; /* přezdívka @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;
/* Volající může přepsat tento název něčím vhodnější pro pozdější použití */
g_source_set_name (source, "MessageQueueSource");
message_queue_source->queue = g_async_queue_ref (queue);
message_queue_source->destroy_message = destroy_message;
/* Přidá zrušitelný zdroj */
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;
}
Zdroje mohou být mnohem složitější, než ve výše uvedeném příkladu. V knihovně libnice je vlastní GSource
zapotřebí k dotazování sady soketů, která se dynamicky mění. Implementace je dána jako ComponentSource
v component.c a předvádí podstatně složitější použití funkce prepare
.
Jiným příkladem je vlastní zdroj k rozhraní GnuTLS s GLib v jeho implementaci GTlsConnection
. GTlsConnectionGnutlsSource
synchronizuje hlavní vlákno s pracovním vláknem TLS, které provádí blokování operací TLS.