Callbacks

Vous pouvez utiliser des types fonction dans vos déclarations C:

lib X
  # In C:
  #
  #    void callback(int (*f)(int));
  fun callback(f : Int32 -> Int32)
end

Vous pouvez ensuite passer une fonction (un Proc) ainsi:

f = ->(x : Int32) { x + 1 }
X.callback(f)

Si vous définissez la fonction comme inline dans le même appel vous pouvez omettre les types des arguments, le compilateur ajoutera pour vous les types en se basant sur la signature de fun:

X.callback ->(x) { x + 1 }

Remarquez, néanmoins, que les fonctions passées au C ne peuvent former des closures. Si le compilateur détecte à la compilation qu'une closure est passée, une erreur sera renvoyée:

y = 2
X.callback ->(x) { x + y } # Error: can't send closure
                           # to C function

Si le compilateur ne peut détecter ça à la compilation, une exception sera levée à l'exécution.

Reportez-vous à la grammaire de type pour la notation utilisée dans les types callbacks et procs.

Si vous voulez passer NULL au lieu d'un callback, passez simplement nil:

# Same as callback(NULL) in C
X.callback nil

Passer une closure à une fonction C

La plupart du temps une fonction C qui permet de définir une callback fournit aussi un argument pour des données personnalisées. Ces données personnalisées sont ensuite envoyées comme argument au callback. Par exemple, supposez qu'une fonction C qui invoque une callback à chaque toc d'horloge, pour passer ce toc d'horloge:

lib LibTicker
  fun on_tick(callback : (Int32, Void* ->), data : Void*)
end

Pour définir proprement un wrapper pour cette fonction nous devons envoyer le Proc comme données du callback, puis convertir ces données de callback en le Proc et finalement l'invoquer.

module Ticker
  # La callback pour l'utilisateur n'a pas de Void*
  def self.on_tick(&callback : Int32 ->)
    # Nous devons enregistrer ça dans l'espace Crystal afin que le ramasse-miettes ne le rammasse pas (*)
    @@callback = callback

    # Etant donné que Proc est un {Void*, Void*}, on ne peut le transformer en Void*,
    # alors nous l'"empaquetons": on alloue de la mémoire et on y stocke le Proc
    boxed_data = Box.box(callback)

    # Nous passons une callback qui ne forme pas de closure, et passons la boxed_data
    # comme données du callback
    LibTicker.on_tick(->(tick, data) {
      # Maintenant nous transformons les données en le Proc, en utilisant Box.unbox
      data_as_callback = Box(typeof(callback)).unbox(data)
      # Et on invoque au final le callback utilisateur
      data_as_callback.call(tick)
    }, boxed_data)
  end
end

Ticker.on_tick do |tick|
  puts tick
end

Remarquez que nous sauvons le callback dans @@callback. La raison est que si nous ne le faisons pas, et que notre code n'y fait plus référence, le ramasse-miettes va le récupérer. La librairie C va bien sûr stocker le callback, mais le ramasse-miettes de Crystal n'a aucun moyen de le savoir.

Attribut Raises

Si une fonction C exécute une callback utilisateur qui peut lever une exception, elle doit être annotée avec l'attribut @[Raises].

Le compilateur infére cet attribut en tant que méthode s'il invoque une méthode qui est marquée comme @[Raises] ou qui lève une exception (récursivement).

Néanmoins, certaines fonctions C acceptent des callbacks qui sont exécutées par des fonctions C. Par exemple, prenons une librairie fictive:

lib LibFoo
  fun store_callback(callback : ->)
  fun execute_callback
end

LibFoo.store_callback ->{ raise "OH NO!" }
LibFoo.execute_callback

Si la callback passée à store_callback lève une exception, alors execute_callback en lévera une aussi. Néanmoins, le compilateur ne sait pas que execute_callback peut potentiellement lever une exception car elle n'est pas marquée avec @[Raises] et le compilateur n'a aucun moyen de le savoir. Dans ces cas vous devez marquer manuellement de telles fonctions:

lib LibFoo
  fun store_callback(callback : ->)

  @[Raises]
  fun execute_callback
end

Si vous ne les marquez pas, les blocs begin/rescue délimitant les appels de cette fonction n'auront pas le résultat attendu.

results matching ""

    No results matching ""