FFIの例:コールバック関数

tcl/tkなど多くのライブラリは,コールバック関数を必用とする.SML#では,単にSML#の関数を引数に渡すだけでコールバック関数を実現できる.さらに,コールバック関数は高階の関数であってもよい.

callbackサンプルは,高階の関数を受け取るCの関数定義の例である.この中の関数

void f1(void(*f)(void(*)(void(*)(void(*)(void(*)(void(*)(void)))))));
void (*(*(*g1(void))(void))(void))(void);

は,MLではそれぞれ以下のような型の関数に対応する.

f1 : (((((unit -> unit) -> unit) -> unit) -> unit) -> unit) -> unit
g1 : unit-> unit -> unit -> unit -> unit

そこで,これらの関数を以下のように使うことができる.

 val lib = DynamicLink.dlopen "./callback.so"
 val sym1 = DynamicLink.dlsym (lib, "f1")
 val sym2 = DynamicLink.dlsym (lib, "g1")
 val f1 = sym1 : _import (((((()->unit)->unit)->unit)->unit)->unit)->unit
 val g1 = sym2 : _import ()->()->()->()->unit

 val () =
     f1 (fn h1 =>
            (print "h1\n";
             h1 (fn h2 =>
                    (print "h2\n";
                     h2 (fn h3 =>
                            print "h3\n")))))

 val g2 = g1 ()
 val g3 = g2 ()
 val g4 = g3 ()
 val () = g4 ()

このコードをSML#で評価すると,期待通の以下のような結果が得られる.

 $ gcc -shared -o callback.so callback.c
 $ smlsharp
 SML# 0.10 (2007-03-17 11:39:26)
 use "callback.sml";
 # val lib = 0x083e11d0 : unit ptr
 val sym1 = 0x4515489c : unit ptr
 val sym2 = 0x45154916 : unit ptr
 val f1 =
     fn : (((((unit  -> unit)  -> unit)  -> unit)  -> unit)  -> unit)  -> unit
 val g1 = fn : unit  -> unit  -> unit  -> unit  -> unit
 f1
 h1
 f2
 h2
 f3
 h3
 g1
 val g2 = fn : unit  -> unit  -> unit  -> unit
 g2
 val g3 = fn : unit  -> unit  -> unit
 g3
 val g4 = fn : unit  -> unit
 g4
 #