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.