Blob Blame History Raw
<?xml version="1.0" encoding="utf-8"?>
<page xmlns="http://projectmallard.org/1.0/" xmlns:its="http://www.w3.org/2005/11/its" xmlns:xi="http://www.w3.org/2003/XInclude" type="guide" style="task" id="custom-gsource.c" xml:lang="cs">

  <info>
    <link type="guide" xref="c#examples"/>

    <credit type="author copyright">
      <name>Philip Withnall</name>
      <email its:translate="no">philip.withnall@collabora.co.uk</email>
      <years>2015</years>
    </credit>

    <include xmlns="http://www.w3.org/2001/XInclude" href="legal.xml"/>

    <desc>Výuka psaní vlastní implementace <code>GSource</code>.</desc>
  </info>

  <title>Vlastní GSource</title>

  <synopsis>
    <title>Souhrn</title>

    <p>Tento článek je výukou ve vytváření vlastních <code>GSource</code>. Ohledně referenční dokumentace se podívejte do <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#GSource">referenční příručky k API knihovny GLib</link>.</p>
  </synopsis>

  <section id="what-is-gsource">
    <title>Co je to <code>GSource</code>?</title>

    <p><link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#GSource"><code>GSource</code></link> 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.</p>

    <p>GLib obsahuje různé typy <code>GSource</code>, ale umožňuje také aplikacím definovat si své vlastní, díky čemuž můžete začlenit vlastní události do hlavní smyčky.</p>

    <p>Struktura <code>GSource</code> a jeho virtuální funkce jsou podrobně zdokumentovány v <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#GSourceFuncs">referenční příručce k API GLib</link>.</p>
  </section>

  <section id="queue-source">
    <title>Zdroj „fronta zpráv“</title>

    <p>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).</p>

    <p>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ý <code>GSource</code> pomocí <code>g_source_attach()</code>. Pro opravdu velké množství zpráv to znamená velký počet přidělování a uvolňování zdrojů <code>GSource</code>.</p>

    <section id="gsource-structure">
      <title>Struktura</title>

      <p>Nejprve potřebujeme deklarovat strukturu pro zdroj. Ta musí obsahovat <code>GSource</code> jako svého rodiče, následovaného soukromými poli pro zdroj: frontou <code>queue</code> a funkcí <code>destroy_message</code>, která se volá, aby po dokončení práce se zprávou, zprávu uvolnila z paměti.</p>
      <code mime="text/x-csrc">
typedef struct {
  GSource         parent;
  GAsyncQueue    *queue;  /* vlastněno */
  GDestroyNotify  destroy_message;
} MessageQueueSource;</code>
    </section>

    <section id="prepare-function">
      <title>Funkce prepare()</title>

      <p>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.</p>
      <code mime="text/x-csrc">
return (g_async_queue_length (message_queue_source-&gt;queue) &gt; 0);</code>
    </section>

    <section id="check-function">
      <title>Funkce check()</title>

      <p>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 <code>NULL</code> v <code>GSourceFuncs</code> funkce check pro tento typ zdroje obejdeme.</p>
    </section>

    <section id="dispatch-function">
      <title>Funkce dispatch()</title>

      <p>U tohoto zdroje je funkce <code>dispatch</code> 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 <code>GSource</code>. Ve frontě nemusí být žádná zpráva: i přesto, že funkce <code>prepare</code> vrátila <code>true</code>, 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 <code>GSource</code> (což je dovolené), musí být zpráva zlikvidována a tiše zahozena.</p>

      <p>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 <code>dispatch</code>. Je to <code>FALSE</code> pro zlikvidování <code>GSource</code> a <code>TRUE</code> pro jeho zachování, stejně jako u <code>GSourceFunc</code> — tyto sémantiky jsou stejné u všech implementací funkce <code>dispatch</code>.</p>
      <code mime="text/x-csrc">
/* Vytáhne zprávu z fronty */
message = g_async_queue_try_pop (message_queue_source-&gt;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-&gt;destroy_message != NULL)
      {
        message_queue_source-&gt;destroy_message (message);
      }

    /* Udrží zdroj pro spotřebování následující zprávy */
    return TRUE;
  }

return func (message, user_data);</code>
    </section>

    <section id="callback">
      <title>Funkce zpětného volání</title>

      <p>Zpětné volání z <code>GSource</code> nemusí mít typ <code>GSourceFunc</code>. Může to být jakýkoliv typ funkce, který je volán ve funkci <code>dispatch</code>, pokud je daný typ dostatečně zdokumentován.</p>

      <p>Normálně se k nastavení funkce zpětného volání pro instanci zdroje používá <code>g_source_set_callback()</code>. Spolu s <code>GDestroyNotify</code> může být udržován silný odkaz kvůli zachování objektu při životě, dokud je existuje zdroj:</p>
      <code mime="text/x-csrc">
g_source_set_callback (source, callback_func,
                       g_object_ref (object_to_strong_ref),
                       (GDestroyNotify) g_object_unref);</code>

      <p>Nicméně, <code>GSource</code> má nepřímou vrstvu pro získání tohoto zpětného volání, vystavenou jako <code>g_source_set_callback_indirect()</code>. Ta umožňuje u <code>GObject</code> nastavit <code>GClosure</code> jako zpětné volání pro zdroj, která umožňuje zdroje, které jsou automaticky likvidovány, když je objekt finalizován – <em>slabý</em> odkaz, v kontrastu k <em>silnému</em> odkazu výše:</p>
      <code mime="text/x-csrc">
g_source_set_closure (source,
                      g_cclosure_new_object (callback_func,
                                             object_to_weak_ref));</code>

      <p>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í:</p>
      <code mime="text/x-csrc">
g_source_set_dummy_callback (source);</code>
    </section>

    <section id="constructor">
      <title>Konstruktor</title>

      <p>Konečně může být napsána definice <code>GSourceFuncs</code> pro <code>GSource</code>, spolu s funkcí konstruktoru. V praxi je typické vystavit nové typy zdrojů jednoduše jako <code>GSource</code>, ne jako podtyp struktury, takže konstruktor vrací <code>GSource*</code>.</p>

      <p>Konstruktor v tomto příkladu rovněž předvádí použití podřízeného zdroje pro podporu pohodlného zrušení. Když je <code>GCancellable</code> 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 <code>GCancellable</code> dostupný jeho zpětnému volání, v podobě parametru s uživatelskými daty zpětného volání v <code>g_source_set_callback()</code>.)</p>
      <code mime="text/x-csrc">
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 (&amp;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-&gt;queue = g_async_queue_ref (queue);
  message_queue_source-&gt;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;
}</code>
    </section>
  </section>

  <section id="full-listing">
    <title>Celý příklad</title>

    <listing>
      <title>Úplný kód příkladu</title>

      <code mime="text/x-csrc">/**
 * 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-&gt;queue) &gt; 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-&gt;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-&gt;destroy_message != NULL)
        {
          message_queue_source-&gt;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-&gt;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 (&amp;result_value, G_TYPE_BOOLEAN);
  g_value_init (&amp;param_value, G_TYPE_POINTER);
  g_value_set_pointer (&amp;param_value, message);

  g_closure_invoke (closure, &amp;result_value, 1, &amp;param_value, NULL);
  retval = g_value_get_boolean (&amp;result_value);

  g_value_unset (&amp;param_value);
  g_value_unset (&amp;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 (&amp;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-&gt;queue = g_async_queue_ref (queue);
  message_queue_source-&gt;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;
}
</code>
    </listing>
  </section>

  <section id="further-examples">
    <title>Další příklady</title>

    <p>Zdroje mohou být mnohem složitější, než ve výše uvedeném příkladu. V knihovně <link href="http://nice.freedesktop.org/">libnice</link> je vlastní <code>GSource</code> zapotřebí k dotazování sady soketů, která se dynamicky mění. Implementace je dána jako <code>ComponentSource</code> v <link href="http://cgit.freedesktop.org/libnice/libnice/tree/agent/component.c#n941">component.c</link> a předvádí podstatně složitější použití funkce <code>prepare</code>.</p>

    <p>Jiným příkladem je vlastní zdroj k rozhraní GnuTLS s GLib v jeho implementaci <code>GTlsConnection</code>. <link href="https://git.gnome.org/browse/glib-networking/tree/tls/gnutls/gtlsconnection-gnutls.c#n871"><code>GTlsConnectionGnutlsSource</code></link> synchronizuje hlavní vlákno s pracovním vláknem TLS, které provádí blokování operací TLS.</p>
  </section>
</page>