flutter/dart: How to use async callback with Dart FFI?

1,782

Solution 1

Agreed, it's not clear how you link against the Dart SDK executable(?)/shared library(?) while building your Android shared library or your iOS code. It compiles, but won't link.

Given that Dart is single-threaded and the async callback technique involves signalling the main Dart thread to indicate that it's time to call down to C to allow a callback to take place on that single, main thread, I've never seen much advantage over, say, polling from Dart (maybe from a timer) to see whether the response is ready. At 60fps, is there much point knowing that the response is ready half way through a 16ms inter-frame interval? The change isn't going to reflect until the next paint anyway. Sure, it doesn't feel very efficient to poll, but once every 16ms isn't that expensive.

Solution 2

It might help to look at my new repository - I managed to have a JUCE (C++) Backend running its own thread that can call Flutter UI: JucyFluttering

Share:
1,782
kakyo
Author by

kakyo

The more you learn, the more you hesitate.

Updated on December 23, 2022

Comments

  • kakyo
    kakyo over 1 year

    My app's backend is written in C++ and the frontend in Dart/flutter. I'd love to have the backend notify frontend whenever data is ready. This requires implementing an async callback scheme between Dart and C++.

    Environment

    $ flutter doctor -v
    [✓] Flutter (Channel stable, 1.20.1, on Mac OS X 10.15.5 19F101, locale
        en-CN)
        • Flutter version 1.20.1 at /Applications/Android/flutter
        • Framework revision 2ae34518b8 (2 days ago), 2020-08-05 19:53:19 -0700
        • Engine revision c8e3b94853
        • Dart version 2.9.0
        • Pub download mirror https://pub.flutter-io.cn
        • Flutter download mirror https://storage.flutter-io.cn
    
     
    [✓] Android toolchain - develop for Android devices (Android SDK version
        30.0.1)
        • Android SDK at /Applications/Android/sdk
        • Platform android-30, build-tools 30.0.1
        • ANDROID_HOME = /Applications/Android/sdk
        • ANDROID_SDK_ROOT = /Applications/Android/sdk
        • Java binary at: /Applications/Android
          Studio.app/Contents/jre/jdk/Contents/Home/bin/java
        • Java version OpenJDK Runtime Environment (build
          1.8.0_242-release-1644-b3-6222593)
        • All Android licenses accepted.
    
    [✓] Xcode - develop for iOS and macOS (Xcode 11.6)
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Xcode 11.6, Build version 11E708
        • CocoaPods version 1.8.4
    
    [✓] Android Studio (version 4.0)
        • Android Studio at /Applications/Android Studio.app/Contents
        • Flutter plugin version 48.0.2
        • Dart plugin version 193.7361
        • Java version OpenJDK Runtime Environment (build
          1.8.0_242-release-1644-b3-6222593)
    
    [✓] VS Code (version 1.47.3)
        • VS Code at /Applications/Visual Studio Code.app/Contents
        • Flutter extension version 3.8.1
    
     
    [!] Connected device                          
        ! No devices available
    

    What I did

    I looked at the github example from the Dart team.

    To adapt the sample code to a flutter project, I did the following

    • Create a FFI plugin project
    • Replace the main function with the sample function
    • Remove unimportant dependencies such as expect
    • Create native library using the referenced C++ code such as dart-sdk-master/runtime/bin/ffi_test/ffi_test_functions.cc

    Problems

    However, I couldn't get the sample to run.

    Dart version

    The first problem I had was that the dart version was not supported when I run the sample directly. I'm on latest stable channel, and also tried dev channel. The error says

    Error: The specified language version is too high. The highest supported language version is 2.9.
    

    Native compilation

    The information on the native-side implementation is missing in the example. So I'm left with lots of compiler errors.

    Launching lib/main.dart on ONEPLUS A6000 in debug mode...
    Running Gradle task 'assembleDebug'...
    
    FAILURE: Build failed with an exception.
    
    * What went wrong:
    Execution failed for task ':app:externalNativeBuildDebug'.
    > Build command failed.
      Error while executing process /Applications/Android/sdk/cmake/3.6.4111459/bin/cmake with arguments {--build /path/to/ffiasync/example/android/app/.cxx/cmake/debug/armeabi-v7a --target ffiasync}
      [1/2] Building CXX object CMakeFiles/ffiasync.dir/path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc.o
      [2/2] Linking CXX shared library /path/to/ffiasync/example/build/app/intermediates/cmake/debug/obj/armeabi-v7a/libffiasync.so
      FAILED: : && /Applications/Android/sdk/ndk/21.3.6528147/toolchains/llvm/prebuilt/darwin-x86_64/bin/clang++  --target=armv7-none-linux-androideabi16 --gcc-toolchain=/Applications/Android/sdk/ndk/21.3.6528147/toolchains/llvm/prebuilt/darwin-x86_64 --sysroot=/Applications/Android/sdk/ndk/21.3.6528147/toolchains/llvm/prebuilt/darwin-x86_64/sysroot -fPIC -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -march=armv7-a -mthumb -Wformat -Werror=format-security   -O0 -fno-limit-debug-info  -Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libgcc_real.a -Wl,--exclude-libs,libatomic.a -static-libstdc++ -Wl,--build-id -Wl,--fatal-warnings -Wl,--exclude-libs,libunwind.a -Wl,--no-undefined -Qunused-arguments -shared -Wl,-soname,libffiasync.so -o /path/to/ffiasync/example/build/app/intermediates/cmake/debug/obj/armeabi-v7a/libffiasync.so CMakeFiles/ffiasync.dir/path/to/ffiasync/lib/ffiasync.cpp.o CMakeFiles/ffiasync.dir/path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc.o  -latomic -lm && :
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:54: error: undefined reference to 'Dart_ExecuteInternalCommand'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:60: error: undefined reference to 'Dart_ExecuteInternalCommand'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:66: error: undefined reference to 'Dart_ExecuteInternalCommand'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:74: error: undefined reference to 'Dart_ExecuteInternalCommand'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:143: error: undefined reference to 'Dart_CurrentIsolate'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:183: error: undefined reference to 'ClobberAndCall'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:277: error: undefined reference to 'Dart_InitializeApiDL(void*)'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:288: error: undefined reference to 'Dart_DumpNativeStackTrace'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:289: error: undefined reference to 'Dart_PrepareToAbort'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_PostCObject_DL'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_NewNativePort_DL'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_PostCObject_DL'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_CloseNativePort_DL'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_PostCObject_DL'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:791: error: undefined reference to 'Dart_NewPersistentHandle'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:793: error: undefined reference to 'Dart_HandleFromPersistent'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:794: error: undefined reference to 'Dart_DeletePersistentHandle'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:795: error: undefined reference to 'Dart_IsError'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:796: error: undefined reference to 'Dart_PropagateError'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:800: error: undefined reference to 'Dart_IsNull'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:802: error: undefined reference to 'Dart_NewWeakPersistentHandle'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:804: error: undefined reference to 'Dart_HandleFromWeakPersistent'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:809: error: undefined reference to 'Dart_DeleteWeakPersistentHandle'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:826: error: undefined reference to 'Dart_IsError'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:829: error: undefined reference to 'Dart_PropagateError'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:846: error: undefined reference to 'Dart_NewStringFromCString'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:847: error: undefined reference to 'Dart_IsError'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:848: error: undefined reference to 'Dart_PropagateError'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:850: error: undefined reference to 'Dart_NewInteger'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:851: error: undefined reference to 'Dart_IsError'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:852: error: undefined reference to 'Dart_PropagateError'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:855: error: undefined reference to 'Dart_Invoke'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:874: error: undefined reference to 'Dart_NewStringFromCString'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:879: error: undefined reference to 'Dart_GetField'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:885: error: undefined reference to 'Dart_IntegerToInt64'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:895: error: undefined reference to 'Dart_EnterScope'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:900: error: undefined reference to 'Dart_ExitScope'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:905: error: undefined reference to 'Dart_True'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_NewPersistentHandle_DL'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_HandleFromPersistent_DL'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_SetPersistentHandle_DL'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_DeletePersistentHandle_DL'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_NewWeakPersistentHandle_DL'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_HandleFromWeakPersistent_DL'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_DeleteWeakPersistentHandle_DL'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_NewPersistentHandle_DL'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_HandleFromPersistent_DL'
      /path/to/ffiasync/lib/ffi_test_functions_vmspecific.cc:0: error: undefined reference to 'Dart_DeletePersistentHandle_DL'
      clang++: error: linker command failed with exit code 1 (use -v to see invocation)
      ninja: build stopped: subcommand failed.
      
    
    
    * Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
    
    * Get more help at https://help.gradle.org
    
    BUILD FAILED in 12s
    Exception: Gradle task assembleDebug failed with exit code 1
    
    

    Questions

    Unable to get answers from Dart team, I have a few questions

    • Should I build Dart runtime from source to make the async callback work? The errors above seems to say I'm missing runtime support. Does that mean FFI async callback is not included in the official dart/flutter releases?
    • Is there a ready-to-run ffi async callback sample or documentation somewhere?

    I'm afraid that with this little info, it would be too difficult to figure it out by trail-and-error.

  • Richard Heap
    Richard Heap over 3 years
    Another old trick is to use a socket as a loopback. Open a server socket on the Dart side, pass the socket number to the C code. The C code connects to the socket. Whenever C wants to signal Dart it writes to the socket, and Dart receives an event. It's like intra-process communication via the O/S.
  • kakyo
    kakyo over 3 years
    Thanks. The 60fps-point makes sense. I eventually went with polling and so far haven't seen major performance issues there.
  • kakyo
    kakyo over 3 years
    Your repo seems a great reference. I'll try your tips when I have time, and will mark your answer as accepted when I make it work. Thank you!
  • Illya S
    Illya S over 3 years
    So do you mean we can do the following? Dart -> C-function -> start new thread -> return from C-function -> return to Dart. Then call another C-function from Dart periodically to check if the thread is completed and results are ready. Will the new thread started from C-function continue even though we exit and return to Dart thread?
  • Illya S
    Illya S over 3 years
    @RichardHeap yes, this actually works. I don't even need to call C-code the second time - only once to start native thread, and then you can monitor completion of the native thread from Dart code - for that you can simply pass pointer to a variable (flag) from Dart code to C-code and then monitor this variable from Dart code. C-code needs to set this flag once the native thread is finished - notifying Dart code about completion.