Philip Withnall
philip.withnall@collabora.co.uk 2015
Výuka psaní vlastní implementace `GSource`.

# Vlastní GSource

#

        Souhrn

        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.

# Co je to `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.

# Zdroj „fronta zpráv“

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`.

## Struktura

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;

## Funkce prepare()

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

## Funkce check()

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.

## Funkce dispatch()

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

## Funkce zpětného volání

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

## Konstruktor

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; }

# Celý příklad

## Úplný kód příkladu

/** * 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 (&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: 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; }

# Další příklady

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.