MLton’s ForeignFunctionInterface allows programs to export SML functions to be called from C. Suppose you would like export from SML a function of type real * char -> int as the C function foo. MLton extends the syntax of SML to allow expressions like the following:
_export "foo": (real * char -> int) -> unit;
The above expression exports a C function named foo, with prototype
Int32 foo (Real64 x0, Char x1);
The _export expression denotes a function of type (real * char -> int) -> unit that when called with a function f, arranges for the exported foo function to call f when foo is called. So, for example, the following exports and defines foo.
val e = _export "foo": (real * char -> int) -> unit;
val _ = e (fn (x, c) => 13 + Real.floor x + Char.ord c)
The general form of an _export expression is
_export "C function name" attr... : cFuncTy -> unit;
The type and the semicolon are not optional. As with _import, a sequence of attributes may follow the function name.
MLton’s -export-header option generates a C header file with prototypes for all of the functions exported from SML. Include this header file in your C files to type check calls to functions exported from SML. This header file includes typedefs for the types that can be passed between SML and C.
Example
Suppose that export.sml is
val e = _export "f": (int * real * char -> char) -> unit;
val _ = e (fn (i, r, _) =>
(print (concat ["i = ", Int.toString i,
" r = ", Real.toString r, "\n"])
; #"g"))
val g = _import "g" public: unit -> unit;
val _ = g ()
val _ = g ()
val e = _export "f2": (Word8.word -> word array) -> unit;
val _ = e (fn w =>
Array.tabulate (10, fn _ => Word.fromLargeWord (Word8.toLargeWord w)))
val g2 = _import "g2" public: unit -> word array;
val a = g2 ()
val _ = print (concat ["0wx", Word.toString (Array.sub (a, 0)), "\n"])
val e = _export "f3": (unit -> unit) -> unit;
val _ = e (fn () => print "hello\n");
val g3 = _import "g3" public: unit -> unit;
val _ = g3 ()
(* This example demonstrates mutual recursion between C and SML. *)
val e = _export "f4": (int -> unit) -> unit;
val g4 = _import "g4" public: int -> unit;
val _ = e (fn i => if i = 0 then () else g4 (i - 1))
val _ = g4 13
val (_, zzzSet) = _symbol "zzz" alloc: (unit -> int) * (int -> unit);
val () = zzzSet 42
val g5 = _import "g5" public: unit -> unit;
val _ = g5 ()
val _ = print "success\n"
Create the header file with -export-header.
% mlton -default-ann 'allowFFI true' \ -export-header export.h \ -stop tc \ export.sml
export.h now contains the following C prototypes.
Int8 f (Int32 x0, Real64 x1, Int8 x2); Pointer f2 (Word8 x0); void f3 (); void f4 (Int32 x0); extern Int32 zzz;
Use export.h in a C program, ffi-export.c, as follows.
#include <stdio.h>
#include "export.h"
/* Functions in C are by default PUBLIC symbols */
void g () {
Char8 c;
fprintf (stderr, "g starting\n");
c = f (13, 17.15, 'a');
fprintf (stderr, "g done char = %c\n", c);
}
Pointer g2 () {
Pointer res;
fprintf (stderr, "g2 starting\n");
res = f2 (0xFF);
fprintf (stderr, "g2 done\n");
return res;
}
void g3 () {
fprintf (stderr, "g3 starting\n");
f3 ();
fprintf (stderr, "g3 done\n");
}
void g4 (Int32 i) {
fprintf (stderr, "g4 (%d)\n", i);
f4 (i);
}
void g5 () {
fprintf (stderr, "g5 ()\n");
fprintf (stderr, "zzz = %i\n", zzz);
fprintf (stderr, "g5 done\n");
}
Compile ffi-export.c and export.sml.
% gcc -c ffi-export.c % mlton -default-ann 'allowFFI true' \ export.sml ffi-export.o
Finally, run export.
% ./export g starting ... g4 (0) success