dart-lang / sdk

The Dart SDK, including the VM, dart2js, core libraries, and more.

Home Page:https://dart.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[vm/ffi] Support asynchronous callbacks

sjindel-google opened this issue · comments

Update Sept 6, 2023: Asynchronous callbacks with void returns can now be done with NativeCallable.listener from Dart 3.1.

Update March 12, 2020: Asynchronous callbacks can be done through the native ports mechanism. See the samples in samples/ffi/async and corresponding c code. In the future we would like to add a more concise syntax that requires less boilerplate code.

An asynchronous callback can be invoked outside the parent isolate and schedules a microtask there.

Asynchronous callbacks can be invoked on an arbitrary thread.

Synchronous callbacks cannot be invoked on an arbitrary thread because it would break the Dart language semantics: only one thread executing Dart per isolate.

Note that we need a solution for managing isolate lifetime when registering an asynchronous callback to be called from any thread in C. If the isolate died, and the callback is called, we have undefined behavior. A possible solution suggested by @mkustermann is to provide call-once callbacks, and keep the isolate alive until they are all called exactly once. Or we could have a counter or an arbitrary condition that we can check.

For inspiration, Kotlin requires the C library to be aware of Kotlin when doing non-main-thread-callbacks:

If the callback doesn't run in the main thread, it is mandatory to init the Kotlin/Native runtime by calling kotlin.native.initRuntimeIfNeeded().

Source: https://github.com/JetBrains/kotlin-native/blob/master/INTEROP.md#callbacks

It's not the thread which the callback is tied to, it's the isolate. So our callbacks may be invoked on any thread so long as that thread has called Dart_EnterIsolate.

It's not the thread which the callback is tied to, it's the isolate. So our callbacks may be invoked on any thread so long as that thread has called Dart_EnterIsolate.

I presume Dart_EnterIsolate also (could) prevent isolates from being shutdown or killed.

Another solution to managing lifetime is that the asynchronous callbacks need to be explicitly de-registered, and the isolate lives until all callbacks are de-registered.

Another challenge is knowing which Isolate the callback should run on. In AOT we have only one trampoline per target method, and there may be indefinitely many isolates from the same source at runtime.

Does it mean that calling back into Dart works as long as Dart_EnterIsolate is called before invoking the callback? How does the native code know that?

We have not made a design for this feature yet, we'll explore the possibilities when creating a design.

Okay, we have a workaround for it:

  • Native: Thread X wants to run callback
  • Native: Thread X adds the callback to a global list of pending callbacks
  • Native: Thread X send sigusr1 to the process
  • Dart: Main Isolate watches sigusr1 (using ProcessSignal.watch) and gets woken up
  • Dart: Main Isolate calls via FFI into a function that executes all pending callbacks

@derolf nice!

Would you like to provide a minimal sample and contribute it as a PR for others to learn from for the time being?

It didn’t work so far because Flutter itself is using SIGUSR1/2 for hot reloading.

However, I saw that a NativePort can be sent through FFI to the c-layer. Do you have any example how to send something to that nativePort from C?

@derolf, to my understanding the NativePort solution only works if you own the Dart embedder yourself.

cc @mkustermann

Linking flutter/flutter#46887.

Okay, I implemented a way that works reliable and clean.

The queue:

On the native side, you need a threadsafe queue that allows to:
-- Enqueue callbacks from some thread that you want to execute in the main isolate (enqueue)
-- Have a function exposed through ffi that blocks until there are callbacks in the queue (wait)
-- Have a function exposed through ffi that executes pending callbacks (execute)

The dance:

Now, you spawn a slave isolate that waits on the queue and then sends a message to the main isolate, and waits again, ... (forever loop)

The main isolate receives this message and calls into ffi to execute all pending callbacks.

If some thread wants to dispatch a callback, you enqueue it instead of calling it. This will wakeup the slave isolate and that will send the said message to main and main will deliver the callback.

That's it. Less than 100 LOC to do it all.

Have a function exposed through ffi that blocks until there are callbacks in the queue (wait)

That is slightly problematic in an event-loop based system. The synchronous blocking will prevent the isolate from processing any other messages (timers, socket i/o, ...).

We'll write an example on how this can be done already now with our dart_native_api.h and ports.

@mkustermann The problem is that none of the dart_native_api.h functions are available/exported in Flutter, so I can't use them. Happy to see your example how you make that work!

The "slave" isolate's one and only job is to do this waiting on the queue. It's doing nothing else. Here's its code (ripped out of the codebase):

class _SlaveIsolateMessage {
  _SlaveIsolateMessage(this.port, this.isolate);
  final SendPort port;
  final int isolate;
}

void _slaveIsolate(_SlaveIsolateMessage msg) {
    print("_slaveIsolate running for ${msg.isolate}");
    while (_dart_ffi_wait_for_callbacks(msg.isolate) != 0) {
      print("_slaveIsolate has callbacks for ${msg.isolate}");
      msg.port.send(1);
    }
    print("_slaveIsolate done for ${msg.isolate}");
    msg.port.send(0);
  }

final _dart_ffi_wait_for_callbacks = dylib.lookupFunction<Int32 Function(Int32), int Function(int)>('dart_ffi_wait_for_callbacks');

The isolate int used to distinguish different native callback queues as we plan to have more than one "main" isolate.

The samples have landed, see referenced commits.

Some status of when this will be available, I saw that the Realm team is just waiting for this feature to be able to launch a Realm for Flutter

Asynchronous callbacks can be today done through the native ports mechanism as illustrated in samples/ffi/async.

In the future we would like to add a more concise syntax that requires less boilerplate code.

Asynchronous callbacks can be today done through the native ports mechanism as illustrated in samples/ffi/async.

In the future we would like to add a more concise syntax that requires less boilerplate code.

As I understand we still depends from APIs declared by dart_native_api.h on the native side so this technique still does not usable with flutter.

As I understand we still depends from APIs declared by dart_native_api.h on the native side
so this technique still does not usable with flutter.

The dart:ffi library exposes the native api symbols now to Dart code via NativeApi.postCObject (as well as NativeApi.closeNativePort, NativeApi.newNativePort). Dart code can make a ReceivePort, obtain it's native port via receivePort.sendPort.nativePort, give that native port as well as the NativeApi.postCObject function pointer to C code. The C code can then post messages on the port, which Dart code can react on.

@katyo Would that work for you?

commented

@mkustermann Can you give a small example with the method you're mentioning? I need this for my app, because it's basically a deal breaker and I don't exactly understand how your method should work in code.
Thanks in advance

@jld3103 the samples are here.

@mkustermann Thanks a lot for short explanation.
As I understand, the idea is exporting needed Dart API calls at runtime.

commented

NativeApi.postCObject cannot use in flutter stable by dart 2.7, has any way to replace this code?
@dcharkes @mkustermann

NativeApi.postCObject cannot use in flutter stable by dart 2.7, has any way to replace this code?
@dcharkes

Hi @yaminet1024, you have to switch to the dev channel to use it. (Until Flutter 1.13 with Dart 2.8 is released.)

I pass native port id as userdata to native function which calls callback with this userdata. So I can access to it in my callback from Dart side.
I need send notification to this port back from Dart. How can I cast native port id back to the SendPort to do it (easy)?
Currently I see one only solution by calling NativeApi.postCObject from Dart but it looks ugly.

Just a quick note: The solution I proposed above works like a charm for us in a Flutter app.

commented

Can you link to your repo if it's opensource?

We have an implementation in a proprietary repository, but I think the whole asynchronous callback machinery was integrated into https://github.com/heremaps/gluecodium

Gluecodium creates bindings to C++ code for Swift/iOS, Java/Android, Dart/Flutter.

Hi all, I set up a simple example of a Flutter plugin with async methods that works via FFI instead of MethodChannels - https://github.com/mikeperri/flutter-native-callbacks-example

Not an expert in any of this stuff, so please let me know if there are any issues. Seems to work well though!

Anyone who is interested in using Dart FFI and Go together find it here: https://github.com/mraleph/go_dart_ffi_example

Thanks to @mraleph 🎉

@dcharkes How do we send a struct value from a native library to Dart via nativeport? Please add examples.

I am trying to send response values, which is a struct, from my native library to Dart. The native library compiles successfully but the dart vm is crashing. Although if I try to send back a int64 value it works fine in the Dart side too.

I am using nativeport for the FFI communication and the native library is written in Go.

File url: https://github.com/ganeshrvel/squash_archiver/blob/feature/go-archiver/packages/archiver_ffi/native/archiver_lib/dart_api_dl/dart_api_dl.go#L54

Interestingly none of the below enum values were able to send the struct value back correctly but the Dart_CObject_kInt64 was able to carry an int64 value.

  Dart_CObject_kNull = 0,
  Dart_CObject_kBool,
  Dart_CObject_kInt32,
  Dart_CObject_kInt64,
  Dart_CObject_kDouble,
  Dart_CObject_kString,
  Dart_CObject_kArray,
  Dart_CObject_kTypedData,
  Dart_CObject_kExternalTypedData,
  Dart_CObject_kSendPort,
  Dart_CObject_kCapability,
  Dart_CObject_kUnsupported,
  Dart_CObject_kNumberOfTypes

@dcharkes How do we send a struct value from a native library to Dart via nativeport? Please add examples.

You would send an Int64 which holds the address of a Pointer<MyStruct>. You send the address from C/C++ by casting a MyStruct* to an int64_t, and you construct the Pointer with fromAddress in Dart, and then use .ref to access the struct.

Note that this way the memory is still managed by C, so you must communicate with C when to free the underlying memory.

In the future it might be possible to do move the ownership of the memory to Dart by using TypedData, but that requires the Dart MyStruct class to be able to be backed by TypedData instead of Pointer<MyStruct> which is still under development.

@dcharkes Thanks for the reply. Actually I kind of figured out how to get it done.

If anyone else is looking for a way to connect Dart and Go via FFI (asynchronously using nativeport) and want to use structs then use this:

Go side

package dart_api_dl

import "C"
import "unsafe"

// #include "stdlib.h"
// #include "stdint.h"
// #include "include/dart_api_dl.c"
//
// // Go does not allow calling C function pointers directly. So we are
// // forced to provide a trampoline.
// bool GoDart_PostCObject(Dart_Port_DL port, Dart_CObject* obj) {
//   return Dart_PostCObject_DL(port, obj);
// }
//
//	typedef struct Work{
//		int64_t a;
//		int64_t b;
//	}Work;
//
//	int64_t GetWork(void **ppWork) {
//		Work *pWork= (Work *)malloc(sizeof(Work));
//		pWork->a=16;
//		pWork->b=15;
//		*ppWork = pWork;
//
//		int64_t ptr = (int64_t)pWork;
//
//		return ptr;
//	}
import "C"

func Init(api unsafe.Pointer) C.long {
	return C.Dart_InitializeApiDL(api)
}

type Work struct {
	a int64
	b int64
}

func SendToPort(port int64, msg int64) {
	var obj C.Dart_CObject
	obj._type = C.Dart_CObject_kInt64

	var pwork unsafe.Pointer
	ptrAddr := C.GetWork(&pwork)

	*(*C.int64_t)(unsafe.Pointer(&obj.value[0])) = ptrAddr

	C.GoDart_PostCObject(C.int64_t(port), &obj)

	defer C.free(pwork)
}

Dart side:

    final interactiveCppRequests = ReceivePort();

    final interactiveCppSub = interactiveCppRequests.listen((data) {
      final work = Pointer<Work>.fromAddress(data as int);
      print(work.ref.a);
      print(work.ref.b);
    });

Find the full code here: https://github.com/ganeshrvel/squash_archiver/tree/feature/go-archiver-example/packages/archiver_ffi/native/archiver_lib

Thanks again.

@dcharkes I have a few questions about the nativeport approach, sort of for the best practices:

  1. Do we need to initialize ReceivePort and nativeport every time we make a FFI request or is it better to keep the native library instance in memory until the app is closed?
  2. For two different FFI calls is it advisable to use a single nativeport and handle the messages from Go separately?

Cheers!

Do we need to initialize ReceivePort and nativeport every time we make a FFI request or is it better to keep the native library instance in memory until the app is closed?

It is cleaner to keep it in memory, but requires a bit more book keeping.

For two different FFI calls is it advisable to use a single nativeport and handle the messages from Go separately?

If the messages are going to the same isolate, it does not matter. Dart execution is single threaded, so the message handling is never concurrent. (Whatever makes your code the cleanest. I've not written lots of code that way, so I cannot advise on on which route to go.)

If the messages are going to different isolates you have to have two different native ports by definition.

You would send an Int64 which holds the address of a Pointer. You send the address from C/C++ by casting a MyStruct* to an int64_t, and you construct the Pointer with fromAddress in Dart, and then use .ref to access the struct.

Is there a way to send back a dynamic string array to Dart from the native library via nativeports?

I was not able to find much resources on support for vectors in Dart FFI.

You would send an Int64 which holds the address of a Pointer. You send the address from C/C++ by casting a MyStruct* to an int64_t, and you construct the Pointer with fromAddress in Dart, and then use .ref to access the struct.

Is there a way to send back a dynamic string array to Dart from the native library via nativeports?

I was not able to find much resources on support for vectors in Dart FFI.

Vectors are a C++ feature (see #38788), or do you mean something else?

Please keep this issue on asynchronous callbacks, for other questions please open new issues (feel free to tag me).

I would strongly recommend to look into https://github.com/heremaps/gluecodium

It allows to create bindings for Dart, Java, Swift. I did a lot of work myself on the Dart bindings and we use it in a large scale project with thousands of functions.

Do we need to initialize ReceivePort and nativeport every time we make a FFI request or is it better to keep the native library instance in memory until the app is closed?

It is cleaner to keep it in memory, but requires a bit more book keeping.

For two different FFI calls is it advisable to use a single nativeport and handle the messages from Go separately?

If the messages are going to the same isolate, it does not matter. Dart execution is single threaded, so the message handling is never concurrent. (Whatever makes your code the cleanest. I've not written lots of code that way, so I cannot advise on on which route to go.)

If the messages are going to different isolates you have to have two different native ports by definition.

  • Is there a way to dispose or close a nativeport?
  • Do we need to manually close the native port after use?
  • What happens if the nativeport or the ffi instance is disposed off even before the native library returns a value? Does it cause any sort of memory leak or crash?
  • Will a native asynchronous task terminate automatically if the nativeport or the ffi instance is disposed or closed?

@dcharkes

Please see the documentation at:

Is there a way to dispose or close a nativeport?

Yes, there are methods in Dart for closing them in Dart, and there is Dart_CloseNativePort for ports created with Dart_NewNativePort.

Do we need to manually close the native port after use?

Yes, just like an open file.

What happens if the nativeport or the ffi instance is disposed off even before the native library returns a value? Does it cause any sort of memory leak or crash?
See documentation on Dart_PostCObject.

 * If true is returned, the message was enqueued, and finalizers for external
 * typed data will eventually run, even if the receiving isolate shuts down
 * before processing the message. If false is returned, the message was not
 * enqueued and ownership of external typed data in the message remains with the
 * caller.

Will a native asynchronous task terminate automatically if the nativeport or the ffi instance is disposed or closed?

Not if the native port is closed if the message already has been delivered - the Dart code is not cancelled mid execution.

What do you mean with "ffi instance is disposed or closed"? Do you mean the isolate the Dart code is running on? When an isolate is shut down, no more messages sent to it will be processed.

@dcharkes How do we send a struct value from a native library to Dart via nativeport? Please add examples.

You would send an Int64 which holds the address of a Pointer<MyStruct>. You send the address from C/C++ by casting a MyStruct* to an int64_t, and you construct the Pointer with fromAddress in Dart, and then use .ref to access the struct.

Note that this way the memory is still managed by C, so you must communicate with C when to free the underlying memory.

In the future it might be possible to do move the ownership of the memory to Dart by using TypedData, but that requires the Dart MyStruct class to be able to be backed by TypedData instead of Pointer<MyStruct> which is still under development.

I want to return a complex object (like Image object, or jni's jobject) to dart. How to define the struct in the dart side? The ffi just support the basic native type.

Update March 12, 2020: Asynchronous callbacks can be done through the native ports mechanism. See the samples in samples/ffi/async. In the future we would like to add a more concise syntax that requires less boilerplate code.

An asynchronous callback can be invoked outside the parent isolate and schedules a microtask there.

Where can I find the example's c/c++ code?

@HungerDeng at the moment you can return Pointer<MyStruct> where class MyStruct extends Struct. See tests/ffi_2/function_structs_test.dart for example.

We're working on implementing passing structs by value directly. This is tracked in #36730.

@HungerDeng at the moment you can return Pointer<MyStruct> where class MyStruct extends Struct. See tests/ffi_2/function_structs_test.dart for example.

We're working on implementing passing structs by value directly. This is tracked in #36730.

I want to get the jobject*(jni) by ffi to dart. And the dart code's struct hold the Pointer<JObject> variate. Then I want to pass the Pointer<JObject> to the flutter engine. Do you have any suggestions about how to recreate the jobject* in the c++(flutter engine) from the dart Pointer<JObject> ?
Thank you in advance.

Please describe your actual problem. What do you want to do with the jobject from within Dart?

Where can I find the example's c/c++ code?

Actually I have the same question. I see many references to the following example how to receive async callback in Dart code from C/C++ code:
https://github.com/dart-lang/sdk/blob/master/samples/ffi/async/sample_async_callback.dart

While this is only Dart part - where is corresponding C/C++ part?

@lumarama is this what you are looking for: https://github.com/dart-lang/sdk/blob/master/runtime/bin/ffi_test/ffi_test_functions_vmspecific.cc ?

Yes! Thanks! I think this is exactly what I was looking for. BTW, what is Opaque type used in the sample_async_callback.dart?

class Work extends Opaque {}

I'm wondering if I can do the following. I mean - I know I can. This is a working code. But may be there is some catch why I should avoid doing this. So basically I create list of Uint8 in Dart and pass it to my C-code. C-code will start several parallel threads and return to Dart. Length of the list is equal to the number of threads. Then I check the list in my Dart code periodically to know when all C-thread finished. Obviously C-threads will set corresponding list element to 1 when finished. This way I can wait parallel C-threads to finish from Dart code and not occupying my main Flutter UI thread. BTW, this waiting loop is only running when I'm waiting C-code to finish - so I don't run it during the entire life-cycle of the application.

  var threadStatusesPtr = xffi.allocate<ffi.Uint8>(count: _numThreads);
  var threadStatuses = threadStatusesPtr.asTypedList(_numThreads);
  for (int i=0; i<threadStatuses.length; i++) {
    threadStatuses[i] = 0;
  }

  // run C-function that starts parallel threads and returns
  // each C-thread when finished writes 1 to corresponding threadStatuses array element
  // so when all C-threads finished all elements in threadStatuses list should be non-zero
  _applyFiltersNative(_numThreads, threadStatusesPtr, ...);

  // waiting for all C-threads to finish
  while (threadStatuses.any((status) => status == 0)) {
    await Future.delayed(Duration(milliseconds: 10));
  }

The reason I'm asking, even though this approach seemed to work, is because once I implemented this approach I started getting random "out of memory" issues - see https://stackoverflow.com/questions/65750426/strange-flutter-app-crash-empty-screen-with-out-of-memory-but-memory-is-fine So I'm wondering if there is any potential issue, why I should not access the same list from Dart code that is being modified in parallel C-threads.

@lumarama If I understand right you updates list items from running threads.
I assume some kind of locking mechanism should be applied to that solution because you have concurrent access to memory from different threads.

@lumarama If I understand right you updates list items from running threads.
I assume some kind of locking mechanism should be applied to that solution because you have concurrent access to memory from different threads.

I was hoping (since implementing synchronization between Dart and C code is hard if possible at all) that you don't really need to synchronize in this case because Dart is always reading, while C-threads are always writing each in a separate list item only. So no synchronization is needed between C-threads. And in worst case Dart will read list items earlier than all C-threads had opportunity to write to them - which should not be a problem - since I will try to read the entire list again in the next loop cycle. So there is no way I can read the list half-modified and miss-interpret it. I expect it to be half-modified and I will wait until all list items are non-zero, which means all C-threads finished.

So I'm almost 100% sure that the issue above is due to async call of my C-function that starts parallel threads and returns to Dart. When I call it synchronously - it never happens. When I call it synchronously I do the following in the end of the C-function - I wait all threads to finish before I return to Dart:

  for (int i=0; i<numThreads; i++) {
      if (pthread_join(_threads[i], NULL) != 0) {
        printf("FFI: Error joining thread\n");
      }
    }

Now when I call it asynchronously I return to Dart without waiting for all threads to finish. Instead I wait those C-threads to finish in Dart by calling another C-function periodically. So I do the following in my Dart code:

 // call C-function that starts several threads and returns to Dart
 _ffiApplyFilters(numThreads, ...);

  // call another C-function periodically until all running threads are completed
  while (_ffiIsApplyFiltersFinished() == 0) {
    await Future.delayed(Duration(milliseconds: 10));
  }

Still, after a few minutes and many successful calls to this function I start getting the following error - as if there is memory leak somewhere. While I'm struggling to understand where. I assume I don't need to wait my thread with pthread_join

pthread_create failed: couldn't mprotect R+W 1028096-byte thread mapping region: Out of memory
...
W/Adreno-GSL(31831): <sharedmem_gpuobj_alloc:2706>: sharedmem_gpumem_alloc: mmap failed errno 12 Out of memory
E/Adreno-GSL(31831): <gsl_memory_alloc_pure:2297>: GSL MEM ERROR: kgsl_sharedmem_alloc ioctl failed.
W/Adreno-GSL(31831): <sharedmem_gpuobj_alloc:2706>: sharedmem_gpumem_alloc: mmap failed errno 12 Out of memory
E/Adreno-GSL(31831): <gsl_memory_alloc_pure:2297>: GSL MEM ERROR: kgsl_sharedmem_alloc ioctl failed.

@lumarama Looks like you're not freeing the memory/closing threads for some reason. Maybe try running your code with valgrind:

dart2native ./test/some_test.dart -o /tmp/t
valgrind --show-mismatched-frees=no --leak-check=full /tmp/t

And maybe try moving this question to stackoverflow - you may have better luck with help there.

I think I've found what was the problem. By default pthread_create() creates joinable threads. That means you have to join them with pthread_join() - overwise they will never release resources allocated. That's why I've never seen this problem with synchronous call - because I waited all my threads with join. When you call your C-func asynchronously you don't want to wait your threads with pthead_join() - in this case you have to create detached threads - i.e. call pthread_detach() after pthread_create() - this way your threads won't hold any resources when finished.

Are there any updates on this issue?

@alexmercerind

Are there any updates on this issue?

Have a look at https://github.com/heremaps/gluecodium . It supports Dart and also async callbacks.

My Windows dynamic library is a third party that cannot be modified. Is there a better way to support it? thanks

My Windows dynamic library is a third party that cannot be modified. Is there a better way to support it? thanks

Not yet.

Note that you could load in another dll which you write yourself and has the required callback signature in C. And then you can use the Native Ports in that dll to call back asynchronously to Dart. And then you pass the function from your own dll as a callback to the library you cannot modify.

My Windows dynamic library is a third party that cannot be modified. Is there a better way to support it? thanks

Not yet.

Note that you could load in another dll which you write yourself and has the required callback signature in C. And then you can use the Native Ports in that dll to call back asynchronously to Dart. And then you pass the function from your own dll as a callback to the library you cannot modify.

I know I could do that, but it just feels a little weird. Haha, thank you very much for your reply

It needs to not only be "waiting for an isolate to become idle", it also needs to be Dart executing thread, otherwise the isolate might wake up and start executing code (for example when it receives a message from a native port).

Can someone explain to me what will happen if I simply pass dart's function pointer to C, and then C program will call it at any time?

It will crash your program (possibly in wild and wondrous ways).

Impossible?

You can achieve it with writing an extra C function.

To achieve what you want:

  1. Make a C function. This is the function you're going to pass to your other C functions as a function pointer.
  2. That C function sends a message to Dart through the native ports. It leaves the actual thing that it wants to execute in some kind of queue, and then blocks execution through some concurrency mechanism. (Thread 1)
  3. Dart receives the message, and calls from Dart into C, and executes the things in the queue doing the callbacks from C back in to Dart and leaves the results of the operations in the queue. (Thread 2: The main Dart execution thread.) And then signals Thread 1 that the results of the work is done, and then returns to Dart.
  4. Thread 1 is now unblocked and can continue its C function returning the answer from the callback that Thread 2 left.

Here is the sample code:

@tatumizer you forget that there can be multiple isolates in the system, so you need to know which one to use. When you call back synchronously we simply use the same isolate that is currently attached to the thread. But when you call back async it's unclear which isolate to use.

For the lack of a better place to ask this question about a native DL API:

when sending a string message from C/C++ to Dart, it isn't clear if the message should be copied (malloc+memcpy) before sending or passed directly.

void dartPostStringMessage(int64_t nativePort, const char* arg) {
    Dart_CObject obj;
    obj.type = Dart_CObject_kString;
    obj.value.as_string = const_cast<char*>(arg);
    Dart_PostCObject_DL(nativePort, &obj);
}

Originally, the type of the as_string member (char* instead of const char*) lead me to believe I should pass a copy of the string, but valgrind reported a leak when I expected dart to take over ownership of a malloced memory...

So... am I correct to assume that Dart_PostCObject_DL() expects an existing const char*. And also that it only accesses the string before returning to the caller (in other words, arg could be freed after Dart_PostCObject_DL(nativePort, &obj);)?

@vaind, feel free to open new issues for questions or use stack overflow.

The string is (decoded and) copied when send as message. So yes, it can be freed after Dart_PostCObject returns.

Dart_PostCObject implementation:

static bool PostCObjectHelper(Dart_Port port_id, Dart_CObject* message) {
ApiMessageWriter writer;
std::unique_ptr<Message> msg =
writer.WriteCMessage(message, port_id, Message::kNormalPriority);
if (msg == nullptr) {
return false;
}
// Post the message at the given port.
return PortMap::PostMessage(std::move(msg));
}
DART_EXPORT bool Dart_PostCObject(Dart_Port port_id, Dart_CObject* message) {
return PostCObjectHelper(port_id, message);
}

WriteCMessage implementation:

case Dart_CObject_kString: {
const uint8_t* utf8_str =
reinterpret_cast<const uint8_t*>(object->value.as_string);
intptr_t utf8_len = strlen(object->value.as_string);
if (!Utf8::IsValid(utf8_str, utf8_len)) {
return false;
}
Utf8::Type type = Utf8::kLatin1;
intptr_t len = Utf8::CodeUnitCount(utf8_str, utf8_len, &type);
if (len > String::kMaxElements) {
return false;
}
// Write out the serialization header value for this object.
WriteInlinedHeader(object);
// Write out the class and tags information.
WriteIndexedObject(type == Utf8::kLatin1 ? kOneByteStringCid
: kTwoByteStringCid);
WriteTags(0);
// Write string length and content.
WriteSmi(len);
if (type == Utf8::kLatin1) {
uint8_t* latin1_str =
reinterpret_cast<uint8_t*>(dart::malloc(len * sizeof(uint8_t)));
bool success =
Utf8::DecodeToLatin1(utf8_str, utf8_len, latin1_str, len);
ASSERT(success);
for (intptr_t i = 0; i < len; i++) {
Write<uint8_t>(latin1_str[i]);
}
::free(latin1_str);
} else {
uint16_t* utf16_str =
reinterpret_cast<uint16_t*>(dart::malloc(len * sizeof(uint16_t)));
bool success = Utf8::DecodeToUTF16(utf8_str, utf8_len, utf16_str, len);
ASSERT(success);
for (intptr_t i = 0; i < len; i++) {
Write<uint16_t>(utf16_str[i]);
}
::free(utf16_str);
}
break;
}

@dcharkes Does the current version of Dart FFI support returning of a Struct from C?

Previously I got it working by returning the pointer of the Struct from C and later processing it inside the Dart.

	bool GoDart_PostCObject(Dart_Port_DL port, int64_t ptrAddr) {
  		Dart_CObject dartObj;
  		dartObj.type = Dart_CObject_kInt64;

		dartObj.value.as_int64 = ptrAddr;

	    return Dart_PostCObject_DL(port, &dartObj);
	}

A lot has changed in few months, I will have to rewrite a lot of the FFI related code now.

  • Is it possible to return a struct by value from C to dart using nativeport?
  • If yes, is there any example or documentation that I can refer to?

Is it possible to return a struct by value from C to dart using nativeport?

No it is not currently.

We could explore returning a TypedData through a native port, and exposing a constructor for structs that allows you to get it backed by a TypedData. (Currently you can only construct structs by FFI trampolines by value or from Pointer.) I've filed #46099 to track this.

Is it possible to return a struct by value from C to dart using nativeport?

No it is not currently.

We could explore returning a TypedData through a native port, and exposing a constructor for structs that allows you to get it backed by a TypedData. (Currently you can only construct structs by FFI trampolines by value or from Pointer.) I've filed #46099 to track this.

Thank you. That's great!

I have a request though, a long standing one and I have requested this multiple times before.

Dart FFI is such a great and powerful feature, but what it lacks right now is a good documentation. The examples and implementations are scattered all over the GitHub repository (example codes and issues). The existing Dart FFI documentation site just points the user to the GitHub example code page.

It's very difficult to know about the available features, changes made to the FFI and migration to the newer version and also it's difficult to keep track of each feature here.

Would it be too early to put out a documentation for Dart FFI?

cc: @dcharkes

Seems to me you could send JSON through the native port and deserialize it in Dart. It's not exactly sending a Struct as a result, but might be useful anyway.

I'm going from memory here, but I do remember that you can enter an Isolate from different threads, just that only one thread at a time can enter any one Isolate. An Isolate is not entered once you're in C/C++ code, right?

Does that help out?

@dcharkes I noticed that the Dart_PostInteger from dart_native_api.h is not exposed for the NativeApi class. Would it be possible to expose it as well ?

@ro99, we've exposed a whole list of dart_api.h functions through dart_api_dl.h, including Dart_PostInteger. Use initializeApiDLData from NativeApi instead to initialize all the symbols from dart_api_dl.h.

without async function call or callback ,dart ffi could be less valuable

without async function call or callback ,dart ffi could be less valuable

Make a concrete C-API proposal and the dart team will for sure consider it.

without async function call or callback ,dart ffi could be less valuable

Make a concrete C-API proposal and the dart team will for sure consider it.

I'm in a strange situation - my library uses native api and channels because I need async callbacks functionally.
In dart 2.15 native bindings support was gone away and my lib cannot build with recent dart anymore. At the same time we have no solution how to implement async callbacks in context of native code. In other words you decided to stop many existing libs without a possibility to upgrade/migrate somehow according new rules.

I'm in a strange situation - my library uses native api and channels because I need async callbacks functionally. In dart 2.15 native bindings support was gone away and my lib cannot build with recent dart anymore. At the same time we have no solution how to implement async callbacks in context of native code. In other words you decided to stop many existing libs without a possibility to upgrade/migrate somehow according new rules.

I'm not sure I understand. We did not deprecate dart_api_dl.h. It is still totally supported to what I mentioned in #37022 (comment) to do async callbacks.

dart_api_dl.h

Thank a lot! Missed that point!

Can someone provide some help on how to include and link dart_api_dl.h and dart_api_dl.c? Do I only need to worry that my library can see dart_api_dl.h (maybe added as header search paths in my C++ library project)?

@vladvoina The Dart SDK contains a folder include. Add that to the header search paths. You also need to add include/dart_api_dl.c to the list of files to compile and link into your app.

Can someone provide some help on how to include and link dart_api_dl.h and dart_api_dl.c? Do I only need to worry that my library can see dart_api_dl.h (maybe added as header search paths in my C++ library project)?

For simplicity on Flutter Android/iOS: https://github.com/Sunbreak/native_interop.tour/blob/3d59a430c4b2f3caf5988486e01b7fa062666859/android/CMakeLists.txt#L3

add_library(native_interop
            # Sets the library as a shared library.
            SHARED
            # Provides a relative path to your source file(s).
            ../ios/Classes/dart_api/dart_api.h
            ../ios/Classes/dart_api/dart_api_dl.h
            ../ios/Classes/dart_api/dart_native_api.h
            ../ios/Classes/dart_api/dart_version.h
            ../ios/Classes/dart_api/internal/dart_api_dl_impl.h
            ../ios/Classes/dart_api/dart_api_dl.c
            ../ios/Classes/native_add.c
            ../ios/Classes/native_sync_callback.cpp
            ../ios/Classes/native_async_callback.cpp)

For cross-platform on Flutter Moblie/Desktop: https://github.com/Sunbreak/cronet_flutter/blob/master/android/CMakeLists.txt#L10-L22

add_library(${PLUGIN_NAME} SHARED
  "../common/dart_api/dart_api_dl.c"
  "../common/native_interop.cpp"
  "../common/sample_url_request_callback.cc"
  "../common/sample_executor.cc"
)

target_include_directories(${PLUGIN_NAME} INTERFACE
  "../common"
  "../common/dart_api"
  "../common/dart_api/internal"
  "../common/cronet"
)

Official package: https://github.com/google/cronet.dart
Official article: https://medium.com/dartlang/google-summer-of-code-2021-results-e514cce50fc
Author article: https://unsuitable001.medium.com/package-cronet-an-http-dart-flutter-package-with-dart-ffi-84f9b69c8a24

Thanks, that worked! (although I decided to keep a copy of the flutter sdk into the repo to avoid having to surgically copy the dart_api files)

That said, I am getting This function declaration in not a prototype warnings when compiling the app from XCode. Seems to come from function signatures with no params like DART_EXPORT void Dart_ShutdownIsolate();. Is it only happening to me?

Thanks, that worked! (although I decided to keep a copy of the flutter sdk into the repo to avoid having to surgically copy the dart_api files)

That said, I am getting 'This function declaration in not a prototypewarnings when compiling the app from XCode. Seems to come from function signatures with no params like DART_EXPORT void Dart_ShutdownIsolate();`. Is it only happening to me?

File another issue please. You could try https://github.com/google/cronet.dart or https://github.com/Sunbreak/cronet_flutter

@Sunbreak: is the libwrapper.so something that could be abstracted into a reusable component? So you register your dart closure with libwrapper which returns a function pointer which is passed to the native library?

@Sunbreak: is the libwrapper.so something that could be abstracted into a reusable component? So you register your dart closure with libwrapper which returns a function pointer which is passed to the native library?

It is possible I think. According to https://cloud.tencent.com/developer/article/1881387:

为了解决以上这些问题,我们希望能够更加方便地调用c++的方法,因此参考grpc/trpc 实现了一套dart::ffi的简单的rpc。在引入这套rpc工具后,对开发效率有显著的提升。在proto上定义dart调用c++的接口,数据结构统一为proto,c++层引入rpc的部分能力,dart层也引入相应的stub,我们去掉rpc的通信机制,改为dart::ffi来进行client和server的通信,只在c++层引入至关重要的服务发现与服务调用。整体的架构如下:

Another way is compile Dart within C++/ObjC: https://github.com/dart-native/dart_native

Why is it a problem that an asynchronous Dart FFI callback could mutate global state? Any C FFI is either unsafe or rather limited. Go allows C to invoke callbacks on another thread, even though Go has a multithreaded garbage collector. I have a feeling that trying to wrap low latency audio libraries like Miniaudio with a C callback that uses a send port to let the main Dart isolate would add some latency. Having to ship extra dynamic libraries in order to wrap FFI callbacks along with my application is also something I'd like to avoid if at all possible.

Here’s a proposal for an API for async callbacks, that I think is generally useful.

/// A callback, that allows native code to call a static Dart function
/// asynchronously.
///
/// Any callback, for which [close] has not been called, will keep the
/// [Isolate] that created it alive.
abstract class AsyncCallback<T extends Function> {
  /// Creates a callback, that allows native code to call a Dart function
  /// asynchronously and blocks the native caller until the Dart function
  /// returns.
  ///
  /// If an exception is thrown while calling [f], the native function will
  /// return [exceptionalReturn], which must be assignable to return type of
  /// [f].
  AsyncCallback(
    @DartRepresentationOf("T") Function f, [
    Object? exceptionalReturn,
  ]);

  /// Creates a callback, that allows native code to call a Dart function
  /// asynchronously and in a non-blocking way.
  ///
  /// The native caller needs to ensure, that the arguments it passes to
  /// [nativeFunction] stay valid until the Dart function returns, and must
  /// not rely on the side effects of the Dart function.
  ///
  /// The [nativeFunction] always returns [nonBlockingReturn], which must be
  /// assignable to the return type of [f]. The return value of the Dart
  /// function is ignored.
  AsyncCallback.nonBlocking(
    @DartRepresentationOf("T") Function f, [
    Object? nonBlockingReturn,
  ]);

  /// C function pointer to a C function, that will call the Dart function.
  ///
  /// The C function can be called from any thread.
  ///
  /// Calling the C function is undefined behavior, after [close] has been
  /// called or the [Isolate] that created the callback has died.
  Pointer<NativeFunction<T>> get nativeFunction;

  /// Closes this callback.
  ///
  /// After this method has been called, [nativeFunction] must not be called
  /// anymore.
  ///
  /// A callback, that has not been closed, will keep the [Isolate] that created
  /// it alive.
  void close();
}

Non-blocking AsyncCallbacks can be useful, if the native caller just notifies of some event or delivers a message and blocking it is detrimental to performance.

It would be useful for non-blocking AsyncCallbacks to allow CObjects in the arguments of the callback.

With this proposal, it would be the responsibility of the Dart code, that created an AsyncCallback, to close it and ensure native code stops calling it, before that point.

It does not specify what happens when multiple AsyncCallbacks are created with the same static Dart function.

I was thinking of just adding named arguments bool async, bool blocking to fromFunction rather than introducing a new API.

Can you elaborate on your reasoning for a separate API?

It would be useful for non-blocking AsyncCallbacks to allow CObjects in the arguments of the callback.

Why use CObjects instead of Dart_Handle in native and Object in Dart? That is how we do it for all the other FFI calls and callbacks. (The fact that it it could use native ports as an implementation mechanism should not leak out.)

With this proposal, it would be the responsibility of the Dart code, that created an AsyncCallback, to close it and ensure native code stops calling it, before that point.

I would give async callbacks the same semantics as synchronous callbacks for now: once allocated they stay alive until the isolate terminates. We should indeed also add an API at some point that can reclaim the memory used for the callbacks. It would indeed be the responsibility of the native code not to call those function pointers anymore after that.

Can you elaborate on your reasoning for a separate API?

I was thinking of an AsyncCallback as something similar to a ReceivePort, which prevents the isolate from exiting while it is not closed. If an isolate does nothing else but create an async callback, pass it to native code and wait for the callback to be called, what would stop the isolate from exiting? The isolate could schedule a timer far in the future and cancel it when it's done receiving callbacks, but that seems a bit like a hack.

Why use CObjects instead of Dart_Handle in native and Object in Dart? That is how we do it for all the other FFI calls and callbacks. (The fact that it it could use native ports as an implementation mechanism should not leak out.)

I was thinking about how a non-blocking caller would be able to pass data that needs to be allocated to an async callback.

Let's take a log callback as an example. It doesn't have to be blocking because the caller just wants to hand a message and maybe a few other values over to Dart code. When native code passes a Dart_CObject to Dart_PostCObject, it is deeply copied and does not need to remain allocated, even though Dart code receives that value asynchronously.

The native Dart API that is currently available through dart_api_dl.h, doesn't allow native code to build a Dart_Handle to a String or List, for example.


BTW, looking forward to acdf82d being available in one of the release channels.
Looks great.

which prevents the isolate from exiting while it is not closed

It would by default not prevents isolates from exiting. Because without a cancel API the isolates would never exit. Also, it's easy to miss.

Instead, I would suggest opening another port from your code, and sending an exit message to that on the last callback from native code, to prevent the isolate from exiting until native code is done with it.

You could implement AsyncCallback as a user like this. (Well, except the fact that we're not supporting generics in FFI callback signatures, so not fully.)

The lack of support for String (#39787) and Lists is a separate issue in my opinion. We would also like to support these for FFI calls and synchronous FFI callbacks, they are not async callback specific. (Side note: For creating a list in native code, we can do callbacks with handles to build the list. Both the dart_api and FFI callbacks do transitions from native code in the VM, so the performance should be better with FFI as it is not reflective. Side note 2: Same for creating a Dart string, we can do a callback that passes in a Pointer, and that gets the string as a return value in a handle.)

Thanks for the explanation. Makes sense to keep the API simple and consistent.

I have come to realise that #47778 might also provide a way to tackle this issue to a degree. Isolate independent functions can be invoked on any thread and then could communicate back to the (isolate dependent) Dart world via ports. I will incorporate these considerations on the initial design.

Cool. Does this mean the flutter engine can be just a regular dynamic library once this issue gets resolved?

@mraleph Having any kind of better support for callbacks in FFI would be a big win. I find that dealing with callbacks from C by far the most difficult thing and roadblock to using FFI with many C libraries. I completely understand that there is the restriction due to the 1 thread per Isolate at a time rule, but that doesn't make life any easier when using FFI.

Looking at the Finalizer proposal doc, it looks great for non-Isolate code, but could it for instance be expanded to something that would work with Isolates? I was thinking maybe it could be expanded by having functionality that creates a new Isolate per each native callback into Dart, thus ensuring that it meets the re-entrancy restriction? With the recent improvements with Isoalate Groups when it comes to Isolate creation performance and mem overhead, could this be a way forward? I guess the main difficulty with "create a new Isolate per callback" approach would be how to wire up ports to existing Isolates to make these automatically created Isolates actually useful, but perhaps thats a more tractable problem and I would think is better than that current workaround of needing us Dart developers to deal with ports/Isolates in the native side code.

@maks If the APIs you are finding challenging to bound to are public please share some details of what you are trying to achieve and where it is failing.

I was thinking maybe it could be expanded by having functionality that creates a new Isolate per each native callback into Dart, thus ensuring that it meets the re-entrancy restriction?

I have thought about this approach before but it raises a question: what are you going to do in that newly created isolate? It is not going to share any memory with another isolate so callback essentially has to be stateless. And if it is stateless then isolate independent code is probably a good enough answer. Alternatively if you are planning to use that freshly created isolate just as a jumping point to another isolate - by sending some data through SendPort then having automatic way to marshal asynchronous invocations to another isolate is good enough answer as well.

The FFI finalizers proposal mentioned possibly allowing Dart code to be compiled into native callbacks so long as it only invokes C functions. This would probably allow sending messages to the main isolate to run the callback there without having to compile a separate DLL alongside your app.

@mraleph good points! What I had in mind was really just moving the current workarounds use of SendPorts into the Dart side vs having to do it in native code. I don't quite follow what you mean by:

having automatic way to marshal asynchronous invocations to another isolate

as reading the finalizers proposal, it talked about running outside any Isolate and only allowing Dart code that could only call FFI code and not reference any other Dart code.

What I had in mind was some sort of mechanism to allow newly created Isolates to be able to get hold of a SendPort from another existing Isolate. Some sort of registry perhaps, so that other Isolates could register SendPorts there and then those SendPorts would be passed in via the spawn() that got called to create these "automatic" Isolates for native callbacks.
For example

// this could be the main Isolate
final receivePort = ReceivePort();
portRegistry.add("sendMeNativeStuff",  receivePort.sendPort);

and then when the "automatic" Isolate is created:

Isolate.spawn(SendPort registeredPort) {
  registeredPort.send(thisIsSomeDataFromNative)
}

I'm not sure if I've explained, so hope the above makes sense?

In regards to which libraries I've run into problems with: its basically any library that has a async callback api. The first one I ran into a while back was libfuse. Since then I've also found that most audio playback libraries use callbacks as well, eg. miniaudio and my initial attempt at a binding.

I don't quite follow what you mean by:

having automatic way to marshal asynchronous invocations to another isolate

I mean we could provide a way to write code like this:

// Create callback from a function [func] which supports being called 
// from a thread which does not have a Dart isolate associated with it.
// If such situation occurs it will instead send a message back to this
// isolate and wait for a response.
final cb = Pointer.fromFunction<Int32 Function(Int32)>(func, detached: true, synchronous: true);

It's not going to work well though for situations which require fast callbacks.

In this cases isolate independent code is likely a better answer. @dcharkes is working on some initial prototypes right now.

My Windows dynamic library is a third party that cannot be modified. Is there a better way to support it? thanks
Is there a better way to support it?

My Windows dynamic library is a third party that cannot be modified. Is there a better way to support it? thanks
Is there a better way to support it?

You can create a new dynamic library that loads the third party library.

The new dynamic library would provide the callback for the third party library and use native ports to call back to Dart.

@dcharkes I have followed the code in samples/ffi/async. Calls to Dart_PostCObject_DL return true, but I never get an event on the dart side that is listening to the ReceivePort.

Do you have any tips on how to debug this?

Edit
I found my issue. Here are some details in case it may help someone in the future..

Here is the basic mechanism from the sample code referenced above:

sequenceDiagram
    participant D as Dart<br>Dart Main Thread
    participant N as Native<br>Dart Main Thread
    participant NT as Native<br> Some Thread
    
    D->>D: Lookup native symbols:<br>initDartApiDL = "InitDartApiDL"<br>executeFunc = "ExecuteFunc"
    D->>N: initDartApiDL
    N->>N: Dart_InitializeApiDL
    D->>D: Create ReceivePort and listen
    activate D
    D->>N: Send SendPort and<br>Dart callback function pointer
    N->>N: Store SendPort and callback for later
    loop Event loop while program is running
        Note over NT: Some event happens that<br>makes you want to invoke Dart callback
        NT->>NT: Create opaque "work" functor<br>(containing Dart callback)<br>Post the functor with Dart_PostCObject_DL
        NT-->>D: Asynchronous exchange of "work" from SendPort to Receive Port
        D->>N: executeFunc<br>(delegating execution of<br>callback to Native on main thread)
        N->>N: reveal "work" to get the<br>Dart callback
        N->>D: callback
    end
    deactivate D

In my case, the event was triggered from Dart and it blocked waiting for the callback to execute. This caused a deadlock and explains why the ReceivePort listen never seemed to receive the work.

sequenceDiagram
    participant D as Dart<br>Dart Main Thread
    participant N as Native<br>Dart Main Thread
    
    D->>D: Lookup native symbols:<br>initDartApiDL = "InitDartApiDL"<br>executeFunc = "ExecuteFunc"
    D->>N: initDartApiDL
    N->>N: Dart_InitializeApiDL
    D->>D: Create ReceivePort and listen
    activate D
    D->>N: Send SendPort and<br>Dart callback function pointer
    N->>N: Store SendPort and callback for later
    loop Event loop while program is running
        D->>N: Some function call (blocking)
        N->>N: Handling function call<br>wants to callback to Dart and is<br>blocking for a result
        N->>N: Create opaque "work" functor<br>(containing Dart callback)<br>Post the functor with Dart_PostCObject_DL
        N-->>D: Asynchronous exchange of "work" from SendPort to Receive Port
        Note over D: Deadlock here!<br>The original Dart function that triggered the event<br>and the native code handling the event are blocking.<br>So the main thread can't service the ReceivePort.
        D->>N: executeFunc<br>(delegating execution of<br>callback to Native on main thread)
        N->>N: reveal "work" to get the<br>Dart callback
        N->>D: callback
    end
    deactivate D

So the obvious fix is to break the interlock between Dart and Native when the event originates from Dart:

sequenceDiagram
    participant D as Dart<br>Dart Main Thread
    participant N as Native<br>Dart Main Thread
    participant NT as Native<br> Some Thread
    
    D->>D: Lookup native symbols:<br>initDartApiDL = "InitDartApiDL"<br>executeFunc = "ExecuteFunc"
    D->>N: initDartApiDL
    N->>N: Dart_InitializeApiDL
    D->>D: Create ReceivePort and listen
    activate D
    D->>N: Send SendPort and<br>Dart callback function pointer
    N->>N: Store SendPort and callback for later
    loop Event loop while program is running
        D->>N: Some function call (blocking)
        N-->>NT: Async dispatch<br>(Unblocks Dart main thread)
        NT->>NT: Handling function call<br>wants to callback to Dart and is<br>blocking for a result
        NT->>NT: Create opaque "work" functor<br>(containing Dart callback)<br>Post the functor with Dart_PostCObject_DL
        NT-->>D: Asynchronous exchange of "work" from SendPort to Receive Port
        D->>N: executeFunc<br>(delegating execution of<br>callback to Native on main thread)
        N->>N: reveal "work" to get the<br>Dart callback
        N->>D: callback
    end
    deactivate D

I was trying to make a Binding of the PortAudio library with dart FFI and I came across this problem, will it be possible in the future to make a Binding with dart FFI of C libraries as well as in Go without having to modify or create a Wrapping in c++ to get around the problem "Cannot invoke native callback outside an isolate." This is very frustrating.

Is there any prospect for the future for this issue to be resolved on the dart side so that I don't have to create another shared library?

import 'dart:convert';
import 'dart:ffi' as ffi;
import 'package:audio_player/portaudio.dart';
import 'package:ffi/ffi.dart' as ffi;
import 'package:path/path.dart' as path;
import 'dart:io' show Directory, exit;

late Portaudio portaudio;

const NUM_SECONDS = 5;
const double SAMPLE_RATE = 44100;
const FRAMES_PER_BUFFER = 64;
const M_PI = 3.14159265;
const TABLE_SIZE = 200;
var err = 0;
void main(List<String> args) {
  var libraryPath =
      path.join(Directory.current.path, 'bin', 'libportaudio64bit.dll');
  var dynamicLibrary = ffi.DynamicLibrary.open(libraryPath);
  portaudio = Portaudio(dynamicLibrary);
  print(portaudio.Pa_GetVersion());
  //init lib
  var result = portaudio.Pa_Initialize();
  checkForError('initialize', result);
  // configure output
  var size = ffi.sizeOf<PaStreamParameters>();
  final ffi.Pointer<PaStreamParameters> outputParameters =
      ffi.malloc.allocate(size);

  outputParameters.ref.channelCount = 2;
  outputParameters.ref.sampleFormat = SampleFormat.float32;
  outputParameters.ref.device = portaudio.Pa_GetDefaultOutputDevice();
  if (outputParameters.ref.device == -1) {
    print("Error: No default output device.\n");
  }
  outputParameters.ref.suggestedLatency =
      portaudio.Pa_GetDeviceInfo(outputParameters.ref.device)
          .ref
          .defaultLowOutputLatency;
  outputParameters.ref.hostApiSpecificStreamInfo = ffi.nullptr;

  // init stream
  final stream = 
 ffi. Pointer<ffi.Pointer<ffi.Void>>.fromAddress(ffi.malloc.allocate(ffi.sizeOf<ffi.Int32>()).address);
//final stream =ffi. Pointer<ffi.Pointer<ffi.Void>>.fromAddress(ffi.malloc.allocate(ffi.sizeOf<ffi.Int32>()).address);


  err = portaudio.Pa_OpenStream(
      stream,
      /* no input */
      ffi.nullptr,
      outputParameters,
      SAMPLE_RATE,
      FRAMES_PER_BUFFER,
      /* we won't output out of range samples so don't bother clipping them */
      paClipOff,
      ffi.Pointer.fromFunction<example_callback>(patestCallback, 1),
      ffi.nullptr);
  print(err);
  checkForError('Pa_OpenStream', err);

  err = portaudio.Pa_StartStream(stream.value);
  checkForError('Pa_StartStream', err);

  print("Play for $NUM_SECONDS seconds. ", );
  portaudio.Pa_Sleep(NUM_SECONDS * 1000);
}

int patestCallback(
    ffi.Pointer<ffi.Void> input,
    ffi.Pointer<ffi.Void> output,
    int frameCount,
    ffi.Pointer<PaStreamCallbackTimeInfo> timeInfo,
    int statusFlags,
    ffi.Pointer<ffi.Void> userData) {
  print('patestCallback');
  return PaStreamCallbackResult.paComplete;
}

void checkForError(String text, int result) {
  if (result != PaErrorCode.paNoError) {
    var errText = portaudio.getErrorText(result);
    print('Error $text - $errText');
    exit(1);
  }
}

class PaTestData extends ffi.Struct {
  @ffi.Array(TABLE_SIZE)
  external ffi.Array<ffi.Float> sine;

  @ffi.Int()
  external int left_phase;

  @ffi.Int()
  external int right_phase;

  @ffi.Array(20)
  external ffi.Array<ffi.Int8> message;
}

extension CharArray on ffi.Array<ffi.Int8> {
  String getDartString(int maxLength) {
    var list = <int>[];
    for (var i = 0; i < maxLength; i++) {
      if (this[i] != 0) list.add(this[i]);
    }
    return utf8.decode(list);
  }

  void setDartString(String s, int maxLength) {
    var list = utf8.encode(s);
    for (var i = 0; i < maxLength; i++) {
      this[i] = i < list.length ? list[i] : 0;
    }
  }
}

https://github.com/PortAudio/portaudio/blob/master/examples/paex_sine.c

PS C:\MyProjectsDart\audio_player> dart .\bin\teste.dart
1246976
0
Play for 5 seconds. 
../../runtime/vm/runtime_entry.cc: 3766: error: Cannot invoke native callback outside an isolate.
version=2.18.3 (stable) (Mon Oct 17 13:23:20 2022 +0000) on "windows_x64"
pid=5224, thread=6900, isolate_group=(nil)(0000000000000000), isolate=(nil)(0000000000000000)    
isolate_instructions=0, vm_instructions=7ff7430566e0
  pc 0x00007ff743260132 fp 0x000000f28e9ff2f0 Dart_IsPrecompiledRuntime+0x21ee92
-- End of DumpStackTrace
PS C:\MyProjectsDart\audio_player> 

@dcharkes I have followed the code in samples/ffi/async. Calls to Dart_PostCObject_DL return true, but I never get an event on the dart side that is listening to the ReceivePort.

Do you have any tips on how to debug this?

Edit I found my issue. Here are some details in case it may help someone in the future..

Here is the basic mechanism from the sample code referenced above:

In my case, the event was triggered from Dart and it blocked waiting for the callback to execute. This caused a deadlock and explains why the ReceivePort listen never seemed to receive the work.

So the obvious fix is to break the interlock between Dart and Native when the event originates from Dart:

Any update? I met the same problem.