[MLton] Exporting closures to C code

Matthew Fluet fluet@cs.cornell.edu
Sun, 25 Sep 2005 16:59:33 -0400 (EDT)


>>> Most C libraries which use callback functions provide some means to
>>> pass a user-supplied pointer to the callback function.  This could be
>>> used to invoke MLton functions from C code (passing the closure
>>> explicitly), even if these functions are not exported.
>>>
>>> How hard would that be to implement?
>>
>> Trivially:
>>
>> (* An  int -> int  function to export to C. *)
>> val () = _export "f_callback": int -> int; f
>>
>> (* Import the address of the exported ML function. *)
>> val f_addr = _address "f_callback" : MLton.Pointer.t
>>
>> (* A C function to register an  int -> int  callback. *)
>> val reg_callback = _import "register_callback": MLton.Pointer.t -> unit;
>>
>> (* Pass the address of the ML function to the registration function. *)
>> val () = reg_callback f_addr
>>
>>
>> This has the very minor disadvantage of introducing "f_callback" as a
>> public exported C symbol, rather than keeping it entirely private to the
>> ML code.
>
> But I can't use that to write a
>
>  val register_callback : object -> (object -> int -> int) -> ()
>
> function which registers an arbitrary object -> int -> int function
> with a certain object, I think.  If I start to use it for multiple
> objects, the closure address stored with f_callback is overwritten,
> and only the last registered callback is ever called.
>
> Or is this analysis incorrect?

You can do anything you could do in C; if the dynamic behavior of the 
callback function is only determined by the object identity (ie., the 
object argument) then you should be able to export one  object * int -> 
int  function which you pass to the register_callback function.  This 
function inspects the object to determine its dynamic behavior.

If you are ultimately interested in providing/implementing a function 
like:

  new_object_w_callback : (int -> int) -> object

which, upon every invocation creates a new object and registers an 
arbitrary int->int (or even object*int->int) function with it, then it is 
certainly possible.

For that , you need to do something like:

local
   val callbacks : (object * (int -> int)) list ref = [];
   val callback : object * int -> int =
     fn (obj,i) =>
     case List.find (!callbacks, fn (obj',_) => Object.eq(obj,obj')) of
       SOME (_,f) => f i
     | NONE => raise Fail "missing object"
   val () = _export "ML_callback": (object*int -> int) -> unit; callback
   val callback_addr = _address "ML_callback": MLton.Pointer.t
in
   fun new_object_w_callback f =
     let
       val obj = Object.new ()
       val () = callbacks := ((obj,f) :: !callbacks)
       val () = Object.register_callback (obj, callback_addr)
     in
       obj
     end
end

where I'm assuming that the Object module has a signature like:

signature OBJECT = sig
   type t
   val new : unit -> t
   val register_callback : obj * MLton.Pointer.t -> unit
   ...
end

and structure like:

structure Object : OBJECT = struct
   type t = MLton.Pointer.t
   val new = _import "Object_new": unit -> MLton.Pointer.t;
   val register_callback =
     _import "Object_register_callback": t * MLton.Pointer.t -> unit;
   ...
end