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 typedef
s 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 reentrant: 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 reentrant: 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 reentrant: 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 reentrant: 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"
Note that the the reentrant
attribute is used for _import
-ing the
C functions that will call the _export
-ed SML functions.
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