[MLton] Callback to function pointer?

Matthew Fluet fluet@cs.cornell.edu
Tue, 12 Jul 2005 11:15:39 -0400 (EDT)

On Tue, 12 Jul 2005, Wesley W. Terpstra wrote:
> On Tue, Jul 12, 2005 at 08:16:54AM -0400, Matthew Fluet wrote:
> > > MLton can already call methods by function pointer, but it cannot export
> > > methods _to_ function pointer. Would this be difficult? Many C libraries
> > > (especially GUIs and event libraries) use callbacks to C function pointers.
> > 
> > Why do you think that MLton cannot export functions that may be called 
> > indirectly?  An "_export"-ed function is a legitimate C function in the 
> > object file.  If you want a function pointer to that function, take its 
> > address with "&".
> I meant something like this:

Ah, now I see your situation.

So, things to clear up.  First, the "_export" expression has both a static
component and a dynamic component.  To implement exported SML functions,
the compiler creates an array of entry points (essentially, unit -> unit
functions that take care of wrapping the real SML function with fetches
and stores of the C arguments and results).  Each (static) "_export" in
the source maps to one element of the array.  When C calls an "_export"ed
function, it really calls a stub that sets up the C arguments and then
dispatches in to the MLton runtime with a unique integer indicating which 
entry in the array to call.

The dynamic component of an "_export" expression updates the array of 
entry points with the SML function passed to "_export".

Hence, it is inadvisable to evaluate the same static "_export" with 
different SML functions, since, as you observed, the last SML function 
wins.  Also, it is inadvisable to call an "_export"ed function from C 
before the "_export" expression is evaluated in SML, since no SML function 
will have been registered.

Taken together, it is really best to only have "_export" in top-level
declarations -- hence, they will be evaluated exactly once and before
useful SML code evaluates.

Now, if you don't want to leave SML to get the address of an "_export"ed 
function, then that is currently supported, using an (as of yet 
undocumented) feature of MLton FFI.  Essentially,

  _import # "name": MLton.Pointer.t;

imports the address of the symbol "name", rather than fetching the
contents of the symbol.  See doc/examples/ffi/import2.sml for an example.

Hence, we can write your example as follows:

fun clicker1 () = print "clicked callback 1!\n"
val () = (_export "clicker1" : unit -> unit;) clicker1
val clicker1Ptr = _import # "clicker1": MLton.Pointer.t
fun clicker2 () = print "clicked callback 2!\n"
val () = (_export "clicker2" : unit -> unit;) clicker2
val clicker2Ptr = _import # "clicker2": MLton.Pointer.t

val gui_clicked = _import "gui_clicked": MLton.Pointer.t -> unit;
fun setupclick fPtr = gui_clicked fPtr

val () = setupclick clicker1Ptr
val () = setupclick clicker2Ptr

val () = enterTheGuiMainLoop ()

Admittedly, a little more verbose that what you originally wrote, though 
not too bad.  I could imagine allowing:

_export # "name" : (tyArg -> tyRes) -> tyPtr;

to mean:

(fn f => ((_export "name" : tyArg -> tyRes;) f; (import # "name" : tyPtr;))

though that is a little complicated to implement (because we need to 
elaborate before taking apart the type annotation, so it isn't a simple 
syntactic transformation).