7

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.

kakyo
  • 10,460
  • 14
  • 76
  • 140
  • what about sockets between the c++ and dart? – Yadu Aug 08 '20 at 05:14
  • Have you found answer to this question? – Illya S Jan 13 '21 at 21:13
  • @Yadu Thanks for the reminder. I also realized that websocket is probably an easier solution for Dart-C++ comm because it's built into Dart. I'm looking into it. – kakyo Jan 14 '21 at 01:44
  • @ElyaS No unfortunately. But you could check out Dharman's answer below. I haven't got time to check that out but it is worth a look. Feel free to let us know if it works for you. – kakyo Jan 14 '21 at 01:46
  • One thing I didn't understand, please clarify, if backend is cpp then what that's gotta do with dart ffi? I suppose backend is the server – Yadu Jan 14 '21 at 02:52
  • @Yadu I might have mixed-up terminology :P. There are no server/remote computing involved. I simply have a C++ lib that's doing all the heavy lifting except UI. Would it be more clear if I used the term lib? But to be fair, the "background" computing may well be moved to the cloud if possible in the future. – kakyo Jan 14 '21 at 04:11
  • Yeah..I guessed it, it's better to have the server and client architecture between cpp and dart side code of your app, even flutter plugin architecture does the same (mostly a guess) – Yadu Jan 14 '21 at 04:33
  • @Yadu I agree. For now it's not quite feasible for me because latency is a concern, that's why I insisted on an in-process approach. – kakyo Jan 14 '21 at 04:51

2 Answers2

7

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

Dharman
  • 30,962
  • 25
  • 85
  • 135
  • 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! – kakyo Sep 28 '20 at 05:56
5

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.

Richard Heap
  • 48,344
  • 9
  • 130
  • 112
  • 1
    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. – Richard Heap Aug 08 '20 at 14:21
  • Thanks. The 60fps-point makes sense. I eventually went with polling and so far haven't seen major performance issues there. – kakyo Aug 10 '20 at 04:06
  • 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 Jan 13 '21 at 21:16
  • 3
    @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. – Illya S Jan 14 '21 at 16:21