Author: Christian.Queinnec@lip6.fr

## Gestion des exceptions en C et en Objective CAML

Les mécanismes de rupture et reprise de calcul varient selon les langages : C utilise le couple `setjmp`-`longjmp`) et les exceptions Objective CAML, la construction (try .. with, raise). Ces mécanismes ne sont, bien sûr, pas compatibles. C'est-à-dire qu'ils ne conservent pas les mêmes informations à la pose d'un récupérateur. Le fait de pouvoir imbriquer des récupérateurs d'exceptions de nature différente et d'en outrepasser certains pose de réelles difficultés d'implantation pour garantir la sûreté de leur utilisation. C'est pour cela que seules les exceptions Objective CAML sont traitées. Néanmoins rien n'empêche de les déclencher ou de les récupérer en C.
Toutes les macros et les fonctions utilisées dans cette partie sont définies dans le fichier `fail.h`.

### Déclencher une exception prédéfinie

Il est facilement possible de déclencher, depuis une fonction C, les exceptions `Failure` ou `Invalid_argument` du module `Pervasives` en utilisant les fonctions suivantes :

`failwith(s)` | : | déclenche l'exception Failure(s)
`invalid_argument(s)` | : | déclenche l'exception Invalid_argument(s).

Dans les deux cas, `s` est une chaîne de caractères C (`char *`).

### Déclencher une exception quelconque

Le mécanisme pour lever une exception quelconque définie en Objective CAML est semblable à celui utilisé pour appliquer une fermeture. Il faut dans un premier temps enregistrer l'exception par la fonction `register_exception` du module `Callback`. On récupère ensuite la valeur avec la fonction `caml_named_value` (voir page ??). On déclenche l'exception avec l'une des fonctions suivantes :

`raise_constant(e)` | lève l'exception `e`,
`raise_with_arg(e,v)` | lève l'exception `e` avec `v` pour argument,
`raise_with_string(e,s)` | idem, mais la valeur est une copie de la chaîne `s`.

    #include <caml/mlvalues.h>
    #include <caml/memory.h>
    #include <caml/fail.h>

    value division (value v1,value v2)
    {
      CAMLparam2(v1,v2);
      if (!Long_val(v2))
        raise_with_arg(*caml_named_value("exception"),v1) ;
      CAMLreturn Val_long(Long_val(v1)/Long_val(v2)) ;
    }

    # external divise : int -> int -> int = "division" ;;
    external divise : int -> int -> int = "division"
    # exception Division_par_zero of int ;;
    exception Division_par_zero of int
    # Callback.register_exception "exception" (Division_par_zero 0) ;;
    - : unit = ()
    # divise 20 4 ;;
    - : int = 5
    # divise 22 0 ;;
    Uncaught exception: Division_par_zero(22)

### Rattraper une exception

Il est possible de rattraper une exception depuis une fonction C, mais seulement si elle a été déclenchée par l'application d'une fermeture venue d'Objective CAML et non directement depuis une fonction C. Les macros `callback_exn`, `callback2_exn`, `callback3_exn` et `callbackN_exn` fonctionnent comme les macros `callback` simples, sauf que si l'application a déclenché une exception, celle-ci est rendue comme résultat. Le résultat d'un `callback_exn` peut être testé par le prédicat `Is_exception_result(v)` qui retourne 1 si la valeur provient du déclenchement d'une exception et 0 sinon. On retrouve le constructeur de l'exception par appel à la fonction `Extract_exception(v)`.
La fonction C `divise_affiche` appelle une fonction `divise` et teste si le retour est une exception, en utilisant l'appel `callback2_exn`. Si c'est le cas, elle affiche une trace et la redéclenche, sinon elle affiche le résultat.

    #include <stdio.h>
    #include <caml/mlvalues.h>
    #include <caml/memory.h>
    #include <caml/callback.h>
    #include <caml/fail.h>

    value divise_affiche (value v1,value v2)
    {
      CAMLparam2(v1,v2) ;
      CAMLlocal3(div,dbz,res) ;
      div = * caml_named_value("divise") ;
      dbz = * caml_named_value("div_by_0") ;
      res = callback2_exn (div,v1,v2) ;
      if (Is_exception_result(res))
        {
          value exn=Extract_exception(res);
          if (Field(exn,0)==dbz)  printf("division par 0\n") ;
          else printf("autre exception\n");
          fflush(stdout);
          if (Wosize_val(exn)==1)  raise_constant(Field(exn,0)) ;
          else raise_with_arg(Field(exn,0),Field(exn,1)) ;
        }
      printf("resultat = %d\n",Long_val(res)) ;
      fflush(stdout) ;
      CAMLreturn Val_unit ;
    }

    # Callback.register "divise" (/) ;;
    - : unit = ()
    # Callback.register_exception "div_by_0" Division_by_zero ;;
    - : unit = ()
    # external divise_ml : int -> int -> unit = "divise_affiche" ;;
    external divise_ml : int -> int -> unit = "divise_affiche"
    # divise_ml 42 3 ;;
    resultat = 14
    - : unit = ()
    # divise_ml 21 0 ;;
    division par 0
    Uncaught exception: Division_by_zero

Il est donc possible de déclencher une exception en C et de la rattraper en Objective CAML; de même, il est possible de déclencher une exception depuis Objective CAML et la rattraper en C. En revanche, un programme C ne peut pas à lui seul déclencher et rattraper une exception d'Objective CAML.