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.
*
... *
t ->
uarray
| u ref
| u vector
bool
| char
| int
| real
| string
| unit
| word
| word8
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
).