Hi Steve,
Nice to meet you! Yes, I'm the main dev on dart:ffi. Yeah, of course, I'm happy to answer any questions. :-)
A Map<int,Function> is indeed a good solution. It requires you to manage the lifetime of closures. Managing lifetime should be relatively easy if you know you'll call a closure exactly once, otherwise it's more tricky. (Note that any support that we add to dart:ffi for calling closures would also require explicit lifetime tracking, unless we add a C++&Dart combined GC like they did with V8&Browser DOM. So you can't really get around that.)
Unfortunately, bool Dart_PostCObject(Dart_Port port_id, Dart_CObject* message) does not expose sending or receiving Closures (it does not use Dart_Handle but Dart_CObject* as message type, and that does not include closures).
Note: If you want to call back the closures asynchronously, you need to use Ports as the mechanism. Messages can be sent on any thread and get in the Dart event queue, while dart:ffi callbacks must be invoked on the Dart mutator thread. There are two ways to do this. #1 Dispatch in Dart based on a message from a port (
sample), or #2 Send a message to Dart through native Ports and yield the main Dart thread to C to do the callbacks (
sample).
Note 2: The Flutter embedder does not re-expose the dart_api symbols, so we use dynamic linking. Which can be seen in the samples as well.
Currently, I'm working on a feature that would simplify your use case: turning Object on the Dart-side of a FFI trampoline call into a Dart_Handle on the C side. This allows you to pass the closure to C through FFI directly without the Map<int,Function>. You can then use a Dart_PersistentHandle on the C side to manage the lifetime. And use sample#2 as described above to pass the closure back to Dart in a callback.
+Dart FFIÂ for if other people have the same questions.
Kind regards,