I know that it's possible to call C/C++ from Dart using FFI, but it seems that on Android, there's no way to call Dart directly from C/C++.
While Dart_PostCObject
is used to send data from C/C++ to Dart, I couldn't link this method on the Android platform. I also thought about using BinaryMessenger
to send data, but it requires StandardMethodCodec
, which I couldn’t link either.
Finally, while I tried using sockets to send data directly from C/C++ to Dart, is there no way to call Dart directly from C/C++, like how FFI allows Dart to call C/C++?
Yes, you can call Dart directly from C/C++ on Android, but it requires using the Dart Native API dynamic linking support provided by dart_api_dl.h rather than the standard Dart_PostCObject. In particular, the function Dart_PostCObject_DL is designed for this purpose and lets you send messages from native code to Dart. However, to use it successfully, you must ensure that the Dart API dynamic linking is properly initialized in your native library. Typically, this involves calling Dart_InitializeApiDL with a valid native port (often passed in from Dart) before you attempt any callbacks. For example, you might write:
#include "dart_api_dl.h"
// Initialize the Dart API for dynamic linking; usually done once during startup.
if (Dart_InitializeApiDL(nativePort) != true) {
// Handle initialization error appropriately.
}
// A function to send a message (byte array) to Dart.
bool SendMessageToDart(Dart_Port port, const uint8_t* data, size_t length) {
Dart_CObject message;
message.type = Dart_CObject_kUint8Array;
message.value.as_byte_array.length = length;
message.value.as_byte_array.bytes = data;
// Send the message using Dart_PostCObject_DL.
return Dart_PostCObject_DL(port, &message) == true;
}
This approach essentially enables your C/C++ code to “call back” into Dart by posting messages on a Dart port, which Dart can then handle (often in an event loop or message handler).
An alternative strategy, if the above seems too low-level or if you run into linking issues, is to use JNI to communicate with Java and then leverage Flutter’s BinaryMessenger via platform channels to send messages to Dart.
If you run into issues linking directly with dart_api_dl.h or prefer a higher-level abstraction, you can use JNI to call into Java. From Java, you can then use Flutter’s platform channels (via BinaryMessenger) to send messages to Dart.
Step 1: Create a Java Method to Send a Message to Dart
First, add a Java method that uses Flutter’s BinaryMessenger. For example, in your MainActivity.java
package com.example.myapp;
import android.os.Handler;
import android.os.Looper;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodChannel;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "com.example.myapp/native";
// Store the BinaryMessenger for later use.
private BinaryMessenger messenger;
private MethodChannel methodChannel;
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
messenger = flutterEngine.getDartExecutor().getBinaryMessenger();
methodChannel = new MethodChannel(messenger, CHANNEL);
}
// Expose a method to send messages from JNI.
public void sendMessageToDart(String message) {
// Ensure this runs on the UI thread.
new Handler(Looper.getMainLooper()).post(() -> {
methodChannel.invokeMethod("nativeMessage", message);
});
}
}
In Dart, set up a corresponding method channel to listen for these messages:
import 'package:flutter/services.dart';
const channel = MethodChannel('com.example.myapp/native');
void setupNativeMessageHandler() {
channel.setMethodCallHandler((call) async {
if (call.method == 'nativeMessage') {
// Handle the message received from the native side.
String message = call.arguments;
print('Received message from native code: $message');
}
});
}
Step 2: Call the Java Method from Native C/C++ Code Using JNI
Next, from your C/C++ code, use JNI to get a reference to your MainActivity
and call the sendMessageToDart
method. For example:
#include <jni.h>
#include <string.h>
// Example JNI function to be called from your native code.
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapp_MainActivity_nativeSendMessage(JNIEnv *env, jobject thiz, jstring message) {
// Get the MainActivity class.
jclass activityClass = env->GetObjectClass(thiz);
if (activityClass == NULL) return;
// Get the method ID for sendMessageToDart(String).
jmethodID sendMsgMethod = env->GetMethodID(activityClass, "sendMessageToDart", "(Ljava/lang/String;)V");
if (sendMsgMethod == NULL) return;
// Call the method with the provided message.
env->CallVoidMethod(thiz, sendMsgMethod, message);
}
You can then call this JNI function from your other native code as needed. For instance, if you want to send a message from within another native function:
void someNativeFunction(JNIEnv* env, jobject mainActivityObj) {
const char* nativeMessage = "Hello from C++!";
jstring jMessage = env->NewStringUTF(nativeMessage);
// Get the MainActivity class and method.
jclass cls = env->GetObjectClass(mainActivityObj);
jmethodID methodId = env->GetMethodID(cls, "sendMessageToDart", "(Ljava/lang/String;)V");
if (methodId != NULL) {
env->CallVoidMethod(mainActivityObj, methodId, jMessage);
}
env->DeleteLocalRef(jMessage);
}
Both approaches can be adapted based on your project's needs, dependencies, and any linking challenges you might face.