MLton 20210117

# CallingFromCToSML

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

- export.sml

- ffi-export.c