My favourite example at the moment is this one from Retrofitting Effect Handlers onto OCaml. It shows OCaml calling C, C calling an OCaml callback and exceptions crossing those boundaries.
$ cat meander.ml
external ocaml_to_c
: unit -> int = "ocaml_to_c"
exception E1
exception E2
let c_to_ocaml () = raise E1
let _ = Callback.register
"c_to_ocaml" c_to_ocaml
let omain () =
try (* h1 *)
try (* h2 *) ocaml_to_c ()
with E2 -> 0
with E1 -> 42
let _ = assert (omain () = 42)
#include <caml/mlvalues.h>
#include <caml/callback.h>
value ocaml_to_c (value unit) {
caml_callback(*caml_named_value
("c_to_ocaml"), Val_unit);
return Val_int(0);
}
Compile it with OCaml 5.2:
$ ocamlopt --version
5.2.0
$ ocamlopt meander_c.c meander.ml -o meander.exe
$ ./meander.exe
$ echo $?
0
Bonus you can use GDB/LLDB on this to set breakpoints in both OCaml and C.