MLton

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 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

Download