0

I want to create binding to this C function in dart, this C function accepts a function pointer called progress_monitor:

FFI_PLUGIN_EXPORT void* magickSetProgressMonitor(..., const void* progress_monitor,...);

where progress_monitor is a function pointer:

typedef bool(*MagickProgressMonitor)(const char *,const long long,
    const long long,void *);

In dart, I tried creating a function pointer like this:

typedef MagickProgressMonitor = bool Function(String text, int offset, int size, List<int>? clientData);

extension _MagickProgressMonitorExtension on MagickProgressMonitor {
  /// Creates a native function pointer from this method.
  Pointer<Void> toNativeFunctionPointer() {
    return Pointer.fromFunction<Bool Function(Pointer<Char>, LongLong, LongLong, Pointer<Void>)>(this, false).cast();
  }
}

But I get this error:

The type 'bool Function(String, int, int, List<int>?)' must be a subtype of 'Bool Function(Pointer<Char>, LongLong, LongLong, Pointer<Void>)' for 'fromFunction'.

I have 2 questions:

  1. what types should I use with fromFunction<???> to remove the error?
  2. the last parameter to the C function is a void* pointer which can be anything, right now I am trying to represent it as List<int> in dart and unsigned char array in C, but this doesn't seem correct, is there a way to handle this more correctly?

Update:

I intend to use the above code by exposing an api method by something like this:

  void magickSetProgressMonitor(MagickProgressMonitor progressMonitor, [List<int>? clientData]) {
    final Pointer<UnsignedChar> clientDataPtr = clientData?.toUnsignedCharArray() ?? nullptr;
    final Pointer<Void> progressMonitorPtr = progressMonitor.toNativeFunctionPointer();
    Pointer<Void> oldMonitorPtr =
        _bindings.magickSetProgressMonitor(...,progressMonitorPtr,...);
    ...
  }
HII
  • 3,420
  • 1
  • 14
  • 35
  • Have you seen this question? https://stackoverflow.com/questions/61541354/dart-flutter-ffi-foreign-function-interface-native-callbacks-eg-sqlite3-exec/61550940#61550940 – Richard Heap Nov 27 '22 at 15:14

1 Answers1

1

For "void* pointer" you can use Pointer<Void>.

Please see the following code for the appropriate typedefs.

import 'dart:ffi';

import 'package:ffi/ffi.dart';

// void* magickSetProgressMonitor(uint32_t dummy, const void* progress_monitor,...);
// we need a pair of typedefs to describe the signature on both sides
// the two typedefs will look the same if there are just pointers etc
// the differences come in parameters like integers
// this is the C signature
typedef magick_set_progress_monitor = Pointer<Void> Function(
  Uint32, // this is just here as an example to show a 32 bit unsigned
  Pointer<NativeFunction<magick_progress_monitor>>,
);
// this is the Dart signature
typedef magickSetProgressMonitor = Pointer<Void> Function(
  int, // ffi has turned the C uint type to a Dart int
  Pointer<NativeFunction<magick_progress_monitor>>,
);

void main() {
  final nativeLib = DynamicLibrary.open('mydll');
  // lookupFunction is the improved syntax for looking up functions in the lib
  // its generics are the C-style signature followed by the Dart-style
  // it returns a Function that matches the Dart-style typedef
  final mspm = nativeLib
      .lookupFunction<magick_set_progress_monitor, magickSetProgressMonitor>(
    'magickSetProgressMonitor',
  );
  
  // before calling mspm it needs the callback function, so look that up
  // this converts the Dart-style function to a pointer to a native function
  // matching the C-style typedef
  final callbackFunction = Pointer.fromFunction<magick_progress_monitor>(
    callback,
    true,
  );
  
  // and call mspm passing the (dummy, example) int and callback function
  final result = mspm(123, callbackFunction);
}

//typedef bool(*MagickProgressMonitor)(const char *,const long long,
//     const long long,void *);
// callback functions need a C-style typedef (so that FFI knows the int sizes)
typedef magick_progress_monitor = Bool Function(
  Pointer<Utf8>, // the documentation says this is a string, so use Utf8
  LongLong,
  LongLong,
  Pointer<Void>,
);

bool callback(
  Pointer<Utf8> t,
  int offset,
  int extent,
  Pointer<Void> client_data,
) {
  final text = t.toDartString(); // convert the Pointer<Utf8> to Dart string
  // do something here with the values - probably call a Dart function provided by the user
  return true;
}

Update following your edit and questions.

You don't have any choice about the callback. Its typedef must match exactly the C typedef that the C code expects to call. You are correct that you shouldn't be exposing Pointer types to your user, so you should keep the callback function in your code, convert the reason string there, then call your user's function with a Dart String.

Finally, it's also clear that this will never work. This is, as its name suggests, a callback function that is called off the main Dart thread by some background process. The C->Dart callback functionality is restricted to callbacks that are made immediately as part of a Dart->C->Dart call.

Richard Heap
  • 48,344
  • 9
  • 130
  • 112
  • I don't understand the answer very much, what is the difference between `example_foo ` and `ExampleFoo `, they seem to have the exact same definition? Please can you use some more informative names, I am already confused with all these generic types that feel like magic – HII Nov 27 '22 at 15:54
  • And also why did you put `Pointer` in the dart function `callback` ? These ffi types are not meant to be exposed in this function as I want it to be provided by the user – HII Nov 27 '22 at 15:55
  • `For "void* pointer" you can use Pointer` I am not asking about this, I am asking about the dart equivalent of `Pointer`, the C function accepts `void*` which can be any data. what is the equivalent of this in dart? – HII Nov 27 '22 at 15:57
  • I updated the question to make it clear how it is intended to be used – HII Nov 27 '22 at 16:06
  • Before I update the answer, be very suspicious of functions that have names like `ProgressMonitor`. Are these called on a background thread? If so, it won't work as you may not call from C->Dart on anything but the main Dart thread. (In other words, callbacks must be synchronous as in they must be called immediately Dart->C->Dart. – Richard Heap Nov 27 '22 at 16:44
  • they will not be invoked in the `C` function (Dart -> `C` -> Dart) immediately, they will be invoked later (after the C function returns), so ig you mean this function cannot have a binding in dart? If this is the case, anyway please can you update the answer to show how to create binding for a dart function that accepts a string as above (supposing it will be called immediately in the C invokation)? – HII Nov 27 '22 at 16:55
  • I'll update, but look at this answer for converting String to Pointer: https://stackoverflow.com/questions/60270263/how-to-map-dart-string-with-ffi – Richard Heap Nov 27 '22 at 18:08
  • 1
    To answer the other question, no you can't use it if it happens later. So you can devise an alternative of having the C function update some (C) state with the latest progress and then you can poll from Dart to read that state using, say, a Timer. If the state has changed from the last time it was polled, create an event, add to a stream, call a callback, etc etc. If you really want to post an event from a different C thread you need to get into a whole realm of 'ports' which normally isn't worth the trouble. – Richard Heap Nov 27 '22 at 18:12
  • the code (understandably) becomes very verbose quickly, finally I would suggest including your last comment above also in your answer as well as it is an important info, thank you. – HII Nov 28 '22 at 13:14