Next Previous Contents

7. Foreign function interface (FFI)

Please keep in mind that MLton's FFI is not part of Standard ML and that it is quite possible that this interface will change or disappear. That having been said, since MLton compiles to C, it is fairly easy to make calls to C functions from SML, at least when passing and returning simple types like char, int, and double. It is not possible to call SML from C. Suppose you would like to call a C function with the following prototype from SML:

int foo(double d, unsigned char c);

MLton extends the syntax of SML to allow expressions like the following:

_prim "foo": real * char -> int;

This expression returns a function of type real * char -> int whose behavior is implemented by calling the C function whose name is foo. What will actually happen in the C code is that there will be a C variable d of type double, c of type unsigned char, and i of type int, and the following C statement will be emitted.

i = foo(d, c);

The general form of a _prim declaration is

_prim "c function name": ty;

The semicolon is not optional. Here is a grammar for the types that are currently allowed.

Here is the mapping between SML types and C types.

SML type        C type
----------      ----------
bool            int (0 is false, nonzero is true)
char            unsigned char
int             int
real            double
string          char *
unit            void
word            unsigned int
word8           unsigned char
u array         char *
u ref           char *
u vector        char *

Passing or returning tuples or datatypes is not allowed because the representation of these is decided quite late in the compilation process and because many optimizations may cause the representation to change. Arrays, refs, and vectors can only be passed as arguments because C functions are not allowed to allocate in the SML heap. Although the C type of an array, ref, or vector is always char*, in reality, the object is layed out in the natural C representation. You are responsible for doing the cast if you want to keep the C compiler from complaining. Strings are just like char arrays, and are not null terminated, unless you manually do so from the ML side.

One other use of the _prim facility is to use C constants. For example, in basis-library/posix/primitive.sml, you will see the following lines.

type syserror = int
val acces = _prim "EACCES": syserror;

This defines the SML variable acces to be an int whose value is the value of the C variable (macro) EACCESS. In the generated C code, all uses of acces will be replaced by EACCESS. You must be careful with such a constant declaration, because the optimizer assumes that it really is a constant. If you have a C macro whose value is non-constant, then you should declare a C macro of no arguments as a wrapper and declare the SML function as a nullary function like _prim "foo": unit -> int;.

There is an example in the examples/ffi directory that demonstrates the use of the FFI. The Makefile demonstrates how to call MLton to include and link with the appropriate files. Running make should produce an executable ffi, which should output success when run. You should also read section gcc options to familiarize yourself with the MLton options governing the use of gcc (especially -i, -I, -l, and -L).


Next Previous Contents