Home > database >  How to create a pointer to a dart function that takes a String in dart:ffi?
How to create a pointer to a dart function that takes a String in dart:ffi?

Time:11-29

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,...);
    ...
  }

CodePudding user response:

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.

  • Related