I thought it could be useful to mention the approach that I have been
taking for calling C functions. In particular, it avoids the need to
have a family of callN functions. For example, instead of writing
call2 (load_sym lib "sum") (DOUBLE, DOUBLE) DOUBLE
operators --> and &&> are used to construct function specifications that
are given to a single call function:
call (load_sym lib "sum") (DOUBLE &&> DOUBLE --> DOUBLE)
The resulting function takes nested pair arguments rather than an
n-tuple, so we would give the argument e.g. (x & y), where & is an
infixr constructor for a pair type, rather than (x, y). See example 1
in attached.
I didn't extend this scheme for passing parameters by reference because
that was easily dealt with elsewhere (still avoiding the family of
callNretX functions): in my approach, each C function binding has two
levels: a low-level as above and a high-level wrapper that is introduced for
- converting between abstract types and concrete C types
- controlling ownership of C allocated memory
These high level wrappers can also perform the required address/deref
operations for passing parameters by reference. At the lower-level,
parameters passed by reference are just pointers:
val swap_ =
call (get_sym lib "swap") (POINTER &&> POINTER --> VOID);
Then this is wrapped, using operators ---> and &&&>, as follows:
val swap = (withRef INT &&&> withRef INT ---> I) swap_;
The resulting function takes/returns nested pair arguments, so we could
write e.g.
val x & y & () = swap (2 & 3)
See example 2 in attached. For the attached examples, the C library
shared object is built with 'make bin'. Don't forget to include the
example directory in LD_LIBRARY_PATH when invoking poly.
Phil
P.S. The same high-level binding wrappers can be used for both Poly/ML
and MLton by creating a common interface, so reducing compiler specific
code.