Callbacks in Dart: dart:ffi only supports calling static Dart functions from native code

Viewed 429

This post is a duplicate of the Github Issue here.

dart --version Dart SDK version: 2.15.0-116.0.dev (dev) (Thu Sep 16 09:47:01 2021 -0700) on "linux_x64"

I've been looking up examples for callbacks and I have tried to get callbacks working for me in FFI.

My current situation

I have a function in my library which expects a pointer to a function. The bindings for the same generated by ffigen seem correct to me.

int SetCallback(
    CallbackType callback,
  ) {
    return _SetCallback(
      callback,
    );
  }

  late final _SetCallbackPtr =
      _lookup<NativeFunction<Int32 Function(CallbackType)>>(
          'SetCallback');
  late final _SetCallback =
      _SetCallbackPtr.asFunction<int Function(CallbackType)>();

where, typedef CallbackType = Pointer<NativeFunction<Void Function(Uint32)>>;.

What I want to do here is to setup this callback in Dart, pass it to the FFI, essentially using it as my callback as I would have in C. In my API which abstracts away from FFI code (which means I have a class MyLibrary full of static functions that the user will call directly, which in turn calls functions from an object _nativeLibrary of the class MyNativeLibrary I have created), I have:

  static int SetCallback({required CallbackFuncDart callback}) {
    Pointer<NativeFunction<CallbackFunc>> pointer = Pointer.fromFunction(callback);

    int status = _nativeLibrary.SetCallback(
      pointer,
    );
    if (STATUS_OK != status) {
      throw LibLexemeException(status);
    }
    return status;
  }

typedef CallbackFunc = Void Function(Uint32);
typedef CallbackFuncDart = void Function(int);

While the sqlite ffi example states here that

Features which dart:ffi does not support yet:

Callbacks from C back into Dart.

I believe the docs haven't been updated to reflect the changes at the samples here. The samples haven't been very clear due to them not having any C/C++ files, or an idea of how the C functions work. Even so, I think this example contains a segment(last code block) where a Dart function is being passed as a callback which I have replicated in my program. It is not clear to me how this will work but upon trying to compile my program I get:

ERROR: ../lib/library_lexeme.dart:180:74: Error: fromFunction expects a static function as parameter. dart:ffi only supports calling static Dart functions from native code. Closures and tear-offs are not supported because they can capture context.
ERROR:     Pointer<NativeFunction<CallbackFunc>> pointer = Pointer.fromFunction(callback);
2 Answers

The short version is that you can't pass your callnback as an argument:

static int SetCallback({required CallbackFuncDart callback}) {
    Pointer<NativeFunction<CallbackFunc>> pointer = Pointer.fromFunction(callback); // <-- this isn't considered a static function

It's quite annoying but you must use a static function defined ahead of time for your dart callbacks to be called from C.

Apparently for now only static functions can be passed via ffi. But if you have to access an instance's data and you're sure that the instance exists you can use my workaround. I use a static list to the instances. This is stupid and ugly but it works for me:

class CallbackClass {
  static Int8 classCallback(int id) { 
    final instance = instanceList[id];
    return instance.instanceCallback();
  }

  Int8 instanceCallback() { return instanceId; }

  static List<CallbackClass> instanceList = <CallbackClass>[];
  late final int instanceId;

  CallbackClass {
    instanceId = instanceList.length;
    instanceList.insert(instanceId, this);
    myFFImapping.passCallback(instanceId, Pointer.fromFunction<>(classCallback);)
  }
}

I omitted the necessary c code, FFI mapping and casting to correct types for clarity, so it obviously won't compile like this.

Related