[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