[ffi] Reland the iNativeCallable.listener example test.
The documentation changes already relanded. This is just relanding the
example.
Patchset 1 is a pure reland. Other patchsets are fixes.
Original CL:
https://dart-review.googlesource.com/c/sdk/+/326580
Change-Id: I04d24d63f08a351db7a6e43f331904274e28e2d5
Bug: https://github.com/dart-lang/sdk/issues/53435
TEST=samples/ffi/http/test/http_test.dart
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/328681
Commit-Queue: Liam Appelbe <liama@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index d6647ee..60545ce 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -48,6 +48,7 @@
"runtime/bin:process_test",
"runtime/bin:run_vm_tests",
"runtime/vm:kernel_platform_files($host_toolchain)",
+ "samples/ffi/http:fake_http",
"utils/dartdev:dartdev",
"utils/dds:dds",
"utils/kernel-service:kernel-service",
diff --git a/samples/ffi/http/.gitignore b/samples/ffi/http/.gitignore
new file mode 100644
index 0000000..a18c42e
--- /dev/null
+++ b/samples/ffi/http/.gitignore
@@ -0,0 +1,6 @@
+.dart_tool
+.packages
+pubspec.lock
+lib/libfake_http.so
+lib/libfake_http.dylib
+lib/fake_http.dll
diff --git a/samples/ffi/http/BUILD.gn b/samples/ffi/http/BUILD.gn
new file mode 100644
index 0000000..b8b98e6
--- /dev/null
+++ b/samples/ffi/http/BUILD.gn
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, 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.
+
+shared_library("fake_http") {
+ sources = [ "lib/fake_http.cc" ]
+ include_dirs = [ "." ]
+ ldflags = [ "-rdynamic" ]
+}
diff --git a/samples/ffi/http/README.md b/samples/ffi/http/README.md
new file mode 100644
index 0000000..023a324
--- /dev/null
+++ b/samples/ffi/http/README.md
@@ -0,0 +1,12 @@
+This is an example that shows how to use `NativeCallable.listener` to interact
+with a multi threaded native API.
+
+The native API is a fake HTTP library with some hard coded requests and
+responses. To build the dynamic library, run this command:
+
+```bash
+c++ -shared -fpic lib/fake_http.cc -lstdc++ -o lib/libfake_http.so
+```
+
+On Windows the output library should be `lib/fake_http.dll` and on Mac it should
+be `lib/libfake_http.dylib`.
diff --git a/samples/ffi/http/lib/dylib_utils.dart b/samples/ffi/http/lib/dylib_utils.dart
new file mode 100644
index 0000000..0425d78
--- /dev/null
+++ b/samples/ffi/http/lib/dylib_utils.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2023, 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.
+
+import 'dart:ffi';
+import 'dart:io' show File, Platform;
+
+Uri dylibPath(String name, Uri path) {
+ if (Platform.isLinux || Platform.isAndroid || Platform.isFuchsia) {
+ return path.resolve("lib$name.so");
+ }
+ if (Platform.isMacOS) return path.resolve("lib$name.dylib");
+ if (Platform.isWindows) return path.resolve("$name.dll");
+ throw Exception("Platform not implemented");
+}
+
+DynamicLibrary dlopenPlatformSpecific(String name, {List<Uri>? paths}) =>
+ DynamicLibrary.open((paths ?? [Uri()])
+ .map((path) => dylibPath(name, path).toFilePath())
+ .firstWhere((lib) => File(lib).existsSync()));
diff --git a/samples/ffi/http/lib/fake_http.cc b/samples/ffi/http/lib/fake_http.cc
new file mode 100644
index 0000000..21a2987
--- /dev/null
+++ b/samples/ffi/http/lib/fake_http.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2023, 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.
+
+#include <atomic>
+#include <chrono>
+#include <cstring>
+#include <thread>
+
+#if defined(_WIN32)
+#define DART_EXPORT extern "C" __declspec(dllexport)
+#else
+#define DART_EXPORT \
+ extern "C" __attribute__((visibility("default"))) __attribute((used))
+#endif
+
+constexpr char kExampleRequest[] = R"(
+GET / HTTP/1.1
+Host: www.example.com
+)";
+
+constexpr char kExampleResponse[] = R"(
+HTTP/1.1 200 OK
+Content-Length: 54
+Content-Type: text/html; charset=UTF-8
+
+<html>
+ <body>
+ Hello world!
+ </body>
+</html>
+)";
+
+DART_EXPORT void http_get(const char* uri, void (*onResponse)(const char*)) {
+ std::thread([onResponse]() {
+ std::this_thread::sleep_for(std::chrono::seconds(3));
+ onResponse(strdup(kExampleResponse));
+ }).detach();
+}
+
+DART_EXPORT void http_serve(void (*onRequest)(const char*)) {
+ std::thread([onRequest]() {
+ while (true) {
+ std::this_thread::sleep_for(std::chrono::seconds(1));
+ onRequest(strdup(kExampleRequest));
+ }
+ }).detach();
+}
diff --git a/samples/ffi/http/lib/http.dart b/samples/ffi/http/lib/http.dart
new file mode 100644
index 0000000..1fde486
--- /dev/null
+++ b/samples/ffi/http/lib/http.dart
@@ -0,0 +1,97 @@
+// Copyright (c) 2023, 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.
+
+import 'dart:async';
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:ffi/ffi.dart';
+
+import 'dylib_utils.dart';
+
+// Runs a simple HTTP GET request using a native HTTP library that runs
+// the request on a background thread.
+Future<String> httpGet(String uri) async {
+ // Create the NativeCallable.listener.
+ final completer = Completer<String>();
+ void onResponse(Pointer<Utf8> responsePointer) {
+ completer.complete(responsePointer.toDartString());
+ calloc.free(responsePointer);
+ }
+
+ final callback = NativeCallable<HttpCallback>.listener(onResponse);
+
+ // Invoke the native HTTP API. Our example HTTP library runs our GET
+ // request on a background thread, and calls the callback on that same
+ // thread when it receives the response.
+ final uriPointer = uri.toNativeUtf8();
+ nativeHttpGet(uriPointer, callback.nativeFunction);
+ calloc.free(uriPointer);
+
+ // Wait for the response.
+ final response = await completer.future;
+
+ // Remember to close the NativeCallable once the native API is finished
+ // with it, otherwise this isolate will stay alive indefinitely.
+ callback.close();
+
+ return response;
+}
+
+// Start a HTTP server on a background thread.
+void httpServe(void Function(String) onRequest) {
+ // Create the NativeCallable.listener.
+ void onNativeRequest(Pointer<Utf8> requestPointer) {
+ onRequest(requestPointer.toDartString());
+ calloc.free(requestPointer);
+ }
+
+ final callback = NativeCallable<HttpCallback>.listener(onNativeRequest);
+
+ // Invoke the native function to start the HTTP server. Our example
+ // HTTP library will start a server on a background thread, and pass
+ // any requests it receives to out callback.
+ nativeHttpServe(callback.nativeFunction);
+
+ // The server will run indefinitely, and the callback needs to stay
+ // alive for that whole time, so we can't close the callback here.
+ // But we also don't want the callback to keep the isolate alive
+ // forever, so we set keepIsolateAlive to false.
+ callback.keepIsolateAlive = false;
+}
+
+// Load the native functions from a DynamicLibrary.
+late final DynamicLibrary dylib = dlopenPlatformSpecific('fake_http', paths: [
+ Platform.script.resolve('../lib/'),
+ Uri.file(Platform.resolvedExecutable),
+]);
+typedef HttpCallback = Void Function(Pointer<Utf8>);
+
+typedef HttpGetFunction = void Function(
+ Pointer<Utf8>, Pointer<NativeFunction<HttpCallback>>);
+typedef HttpGetNativeFunction = Void Function(
+ Pointer<Utf8>, Pointer<NativeFunction<HttpCallback>>);
+final nativeHttpGet =
+ dylib.lookupFunction<HttpGetNativeFunction, HttpGetFunction>('http_get');
+
+typedef HttpServeFunction = void Function(
+ Pointer<NativeFunction<HttpCallback>>);
+typedef HttpServeNativeFunction = Void Function(
+ Pointer<NativeFunction<HttpCallback>>);
+final nativeHttpServe = dylib
+ .lookupFunction<HttpServeNativeFunction, HttpServeFunction>('http_serve');
+
+Future<void> main() async {
+ print('Sending GET request...');
+ final response = await httpGet('http://example.com');
+ print('Received a response: $response');
+
+ print('Starting HTTP server...');
+ httpServe((String request) {
+ print('Received a request: $request');
+ });
+
+ await Future.delayed(Duration(seconds: 10));
+ print('All done');
+}
diff --git a/samples/ffi/http/pubspec.yaml b/samples/ffi/http/pubspec.yaml
new file mode 100644
index 0000000..2798023
--- /dev/null
+++ b/samples/ffi/http/pubspec.yaml
@@ -0,0 +1,12 @@
+name: http
+version: 0.0.1
+publish_to: none
+
+environment:
+ sdk: '>=3.0.0 <4.0.0'
+
+dependencies:
+ ffi: ^2.1.0
+
+dev_dependencies:
+ test: ^1.21.1
diff --git a/samples/ffi/http/test/http_test.dart b/samples/ffi/http/test/http_test.dart
new file mode 100644
index 0000000..509d8dd
--- /dev/null
+++ b/samples/ffi/http/test/http_test.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2023, 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.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:ffi/ffi.dart';
+import 'package:test/test.dart';
+
+import '../lib/http.dart';
+
+Future<void> main() async {
+ test('httpGet', () async {
+ final response = await httpGet('http://example.com');
+ expect(response, contains('Hello world!'));
+ });
+
+ test('httpServe', () async {
+ final completer = Completer<String>();
+ httpServe((request) => completer.complete(request));
+ final request = await completer.future;
+ expect(request, contains('www.example.com'));
+ });
+}
diff --git a/tools/linux_dist_support/create_tarball.py b/tools/linux_dist_support/create_tarball.py
index b519c43..059aecb 100755
--- a/tools/linux_dist_support/create_tarball.py
+++ b/tools/linux_dist_support/create_tarball.py
@@ -42,7 +42,6 @@
ignoredPaths = [
'buildtools/linux-x64/go',
'buildtools/linux-x64/rust',
- 'samples',
'third_party/7zip',
'third_party/android_tools',
'third_party/clang',