// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. // // Sample showing how to do calls from C into Dart through native ports. // // This sample does not use FFI callbacks to do the callbacks at all. Instead, // it sends a message to Dart through native ports, decodes the message in Dart // does a method call in Dart and sends the result back to C through a native // port. // // The disadvantage of this approach compared to `sample_async_callback.dart` // is that it requires more boilerplate, because it does not use the automatic // marshalling of data of the FFI. // // The advantage is that finalizers can be used when passing ownership of data // (buffers) from C to Dart. import 'dart:ffi'; import 'dart:isolate'; import 'dart:typed_data'; import 'package:expect/expect.dart'; import '../dylib_utils.dart'; var globalResult = 0; var numCallbacks1 = 0; var numCallbacks2 = 0; main() async { print("Dart = Dart mutator thread executing Dart."); print("C Da = Dart mutator thread executing C."); print("C T1 = Some C thread executing C."); print("C T2 = Some C thread executing C."); print("C = C T1 or C T2."); print("Dart: Setup."); Expect.isTrue(NativeApi.majorVersion == 2); Expect.isTrue(NativeApi.minorVersion >= 0); final initializeApi = dl.lookupFunction), int Function(Pointer)>("InitDartApiDL"); Expect.isTrue(initializeApi(NativeApi.initializeApiDLData) == 0); final interactiveCppRequests = ReceivePort()..listen(handleCppRequests); final int nativePort = interactiveCppRequests.sendPort.nativePort; registerSendPort(nativePort); print("Dart: Tell C to start worker threads."); startWorkSimulator2(); // We need to yield control in order to be able to receive messages. while (numCallbacks2 < 3) { print("Dart: Yielding (able to receive messages on port)."); await asyncSleep(500); } print("Dart: Received expected number of callbacks."); Expect.equals(2, numCallbacks1); Expect.equals(3, numCallbacks2); Expect.equals(14, globalResult); print("Dart: Tell C to stop worker threads."); stopWorkSimulator2(); interactiveCppRequests.close(); print("Dart: Done."); } int myCallback1(int a) { print("Dart: myCallback1($a)."); numCallbacks1++; return a + 3; } void myCallback2(int a) { print("Dart: myCallback2($a)."); globalResult += a; numCallbacks2++; } class CppRequest { final SendPort? replyPort; final int? pendingCall; final String method; final Uint8List data; factory CppRequest.fromCppMessage(List message) { return CppRequest._(message[0], message[1], message[2], message[3]); } CppRequest._(this.replyPort, this.pendingCall, this.method, this.data); String toString() => 'CppRequest(method: $method, ${data.length} bytes)'; } 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})'; } 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); final cppResponse = 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); } } final dl = dlopenPlatformSpecific("ffi_test_functions"); final registerSendPort = dl.lookupFunction('RegisterSendPort'); final startWorkSimulator2 = dl.lookupFunction('StartWorkSimulator2'); final stopWorkSimulator2 = dl.lookupFunction('StopWorkSimulator2'); Future asyncSleep(int ms) { return new Future.delayed(Duration(milliseconds: ms), () => true); }