Example: github.com/Sunbreak/na…

Dart –> Native

Dart calls Native methods synchronously

Dart FFI function declaration

// Declaration
final int Function(int x, int y) nativeAdd = nativeInteropLib
    .lookup<NativeFunction<Int32 Function(Int32, Int32)>>("native_add")
    .asFunction();
Copy the code

Use of the Dart FFI function

// Usage
print('nativeAdd(1, 2) = ${nativeAdd(1.2)}');
Copy the code

Native implementation of FFI functions

Native methods need to expose the C interface

// Implementation
__attribute__((visibility("default"))) __attribute__((used))
int32_t native_add(int32_t a, int32_t b) {
    printf("native_add called\n"); // XCode debug print
    return a + b;
}
Copy the code

Native –> Dart

Dart synchronously invokes Native methods and Native synchronously invokes Dart methods

Dart FFI function declaration

// Declaration
typedef NativeSyncCallbackFunc = Int32 Function(Int32 n);

typedef _c_NativeSyncCallback = Void Function(
  Pointer<NativeFunction<NativeSyncCallbackFunc>> callback,
);

typedef _dart_NativeSyncCallback = void Function(
  Pointer<NativeFunction<NativeSyncCallbackFunc>> callback,
);

final _dart_NativeSyncCallback nativeSyncCallback = nativeInteropLib
    .lookup<NativeFunction<_c_NativeSyncCallback>>("NativeSyncCallback")
    .asFunction();
Copy the code

Dart callback function declaration

The Dart callback needs to be a top-level function

// Declaration
int normalSyncCallback(int n) {
  print('normalSyncCallback called');
  return n * n;
}
Copy the code

Use of the Dart FFI function

// Usage
var normalFunc = Pointer.fromFunction<NativeSyncCallbackFunc>(normalSyncCallback, syncExceptionalReturn);
nativeSyncCallback(normalFunc);
Copy the code

Native implementation of FFI functions

C++ implementations need to expose C interfaces

// Implementation
#ifdef __cplusplus
extern "C" {
#endif

typedef int32_t (*CallbackFunc)(int32_t n);

__attribute__((visibility("default"))) __attribute__((used))
void NativeSyncCallback(CallbackFunc callback) {
    std::cout << "NativeSyncCallback callback(9) = " << callback(9) << std::endl; // XCode debug print
}

#ifdef __cplusplus
}
#endif
Copy the code

Native -async-> Dart

Dart calls Native methods synchronously and Native calls back Dart methods asynchronously

Dart FFI function declaration

// Declar
typedef NativeAsyncCallbackFunc = Void Function(a);typedef _c_NativeAsyncCallback = Void Function(
  Pointer<NativeFunction<NativeAsyncCallbackFunc>> callback,
);

typedef _dart_NativeAsyncCallback = void Function(
  Pointer<NativeFunction<NativeAsyncCallbackFunc>> callback,
);

final _dart_NativeAsyncCallback nativeAsyncCallback = nativeInteropLib
    .lookup<NativeFunction<_c_NativeAsyncCallback>>("NativeAsyncCallback")
    .asFunction();
Copy the code

Dart callback function declaration

The Dart callback needs to be a top-level function

// Declaration
void asyncCallback() {
  print('asyncCallback called');
}
Copy the code

Use of the Dart FFI function

// Usage
var asyncFunc = Pointer.fromFunction<NativeAsyncCallbackFunc>(asyncCallback);
nativeAsyncCallback(asyncFunc);
Copy the code

Native implementation of FFI functions

Dart callback function execution needs to be performed on the Mutator Thread of the Dart Isolate that initiated the transformation (i.e., fromFunction). Therefore, Dart callback function pointer must be sent back to the Mutator Thread via SendPort on the Native side to execute the transformation

Reference code: github.com/dart-lang/s…

Initialize the Dart Dynamic Library API

  • The Dart statement
// Declaration
final _initializeApi = nativeInteropLib.lookupFunction<
    IntPtr Function(Pointer<Void>),
    int Function(Pointer<Void>)>("InitDartApiDL");
Copy the code
  • Use the Dart
// Usage
WidgetsFlutterBinding.ensureInitialized();
var nativeInited = _initializeApi(NativeApi.initializeApiDLData);
Copy the code
  • Native statement

From github.com/dart-lang/s…

// Declaration
DART_EXTERN intptr_t Dart_InitializeApiDL(void* data);
Copy the code

Registered SendPort

  • The Dart statement
// Declaration
final _registerSendPort = nativeInteropLib.lookupFunction<
    Void Function(Int64 sendPort),
    void Function(int sendPort)>('RegisterSendPort');
Copy the code
  • Use the Dart
// Usage
_registerSendPort(_receivePort.sendPort.nativePort);
Copy the code
  • Native implementation
// Implementaion
Dart_Port send_port_;

DART_EXPORT void RegisterSendPort(Dart_Port send_port) {
  send_port_ = send_port;
}
Copy the code

Native implementation of Dart FFI functions

// Implementation
DART_EXPORT void NativeAsyncCallback(VoidCallbackFunc callback) {
    printf("NativeAsyncCallback Running on (%p)\n".pthread_self());
    
    pthread_t callback_thread;
    pthread_create(&callback_thread, NULL, thread_func, (void *)callback);
}
Copy the code

Native sends the callback function pointer to Dart

void *thread_func(void *args) {
    printf("thread_func Running on (%p)\n".pthread_self());
    sleep(1 /* seconds */); // doing something

    Dart_CObject dart_object;
    dart_object.type = Dart_CObject_kInt64;
    dart_object.value.as_int64 = reinterpret_cast<intptr_t>(args);
    Dart_PostCObject_DL(send_port_, &dart_object);

    pthread_exit(args);
}
Copy the code

Dart receives the callback function pointer

void _handleNativeMessage(dynamic message) {
  print('_handleNativeMessage $message');
  final int address = message;
  _executeCallback(Pointer<Void>.fromAddress(address).cast());
}
Copy the code

Execute the callback function

Because function pointer is Native pointer, it cannot be restored to Dart function at present and needs to be transferred to Native side for execution

  • The Dart statement
// Declaration
final _executeCallback = nativeInteropLib.lookupFunction<_c_NativeAsyncCallback,
    _dart_NativeAsyncCallback>('ExecuteCallback');
Copy the code
  • Use the Dart
// Usage
_executeCallback(Pointer<Void>.fromAddress(address).cast());
Copy the code
  • Native implementation
// Implementaion
DART_EXPORT void ExecuteCallback(VoidCallbackFunc callback) {
    printf("ExecuteCallback Running on (%p)\n".pthread_self());
    callback(a); }Copy the code

You can see that NativeAsyncCallback and ExecuteCallback are executed on the same thread, the Mutator thread of the Dart Isolate, while thread_func is executed on another thread

Native -messge-> Dart

Generally, messages are sent to the Dart side in a message manner and the Dart side decides how to handle callbacks, similar to the MessageChannel of Flutter

Reference code: github.com/dart-lang/s…

  • The message definition
// Declaration
class CppResponse {
  final int pendingCall;
  final Uint8List data;

  CppResponse(this.pendingCall, this.data);

  List toCppMessage() => List.from([pendingCall, data], growable: false);

  String toString() => 'CppResponse(message: ${data.length}) ';
}
Copy the code
  • message
void handleCppRequests(dynamic message) {
  final cppRequest = CppRequest.fromCppMessage(message);
  print('Dart:   Got message: $cppRequest');

  if (cppRequest.method == 'myCallback1') {
    // Use the data in any way you like. Here we just take the first byte as
    // the argument to the function.
    final int argument = cppRequest.data[0];
    final int result = myCallback1(argument);
    finalcppResponse = CppResponse(cppRequest.pendingCall! , Uint8List.fromList([result]));print('Dart:   Responding: $cppResponse'); cppRequest.replyPort! .send(cppResponse.toCppMessage()); }else if (cppRequest.method == 'myCallback2') {
    final int argument = cppRequest.data[0]; myCallback2(argument); }}Copy the code