mirror of
https://github.com/dart-lang/sdk
synced 2024-09-20 23:01:19 +00:00
[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>
This commit is contained in:
parent
d43ad21d88
commit
baaf5e1116
1
BUILD.gn
1
BUILD.gn
|
@ -48,6 +48,7 @@ group("runtime") {
|
||||||
"runtime/bin:process_test",
|
"runtime/bin:process_test",
|
||||||
"runtime/bin:run_vm_tests",
|
"runtime/bin:run_vm_tests",
|
||||||
"runtime/vm:kernel_platform_files($host_toolchain)",
|
"runtime/vm:kernel_platform_files($host_toolchain)",
|
||||||
|
"samples/ffi/http:fake_http",
|
||||||
"utils/dartdev:dartdev",
|
"utils/dartdev:dartdev",
|
||||||
"utils/dds:dds",
|
"utils/dds:dds",
|
||||||
"utils/kernel-service:kernel-service",
|
"utils/kernel-service:kernel-service",
|
||||||
|
|
6
samples/ffi/http/.gitignore
vendored
Normal file
6
samples/ffi/http/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
.dart_tool
|
||||||
|
.packages
|
||||||
|
pubspec.lock
|
||||||
|
lib/libfake_http.so
|
||||||
|
lib/libfake_http.dylib
|
||||||
|
lib/fake_http.dll
|
9
samples/ffi/http/BUILD.gn
Normal file
9
samples/ffi/http/BUILD.gn
Normal file
|
@ -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" ]
|
||||||
|
}
|
12
samples/ffi/http/README.md
Normal file
12
samples/ffi/http/README.md
Normal file
|
@ -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`.
|
20
samples/ffi/http/lib/dylib_utils.dart
Normal file
20
samples/ffi/http/lib/dylib_utils.dart
Normal file
|
@ -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()));
|
48
samples/ffi/http/lib/fake_http.cc
Normal file
48
samples/ffi/http/lib/fake_http.cc
Normal file
|
@ -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();
|
||||||
|
}
|
97
samples/ffi/http/lib/http.dart
Normal file
97
samples/ffi/http/lib/http.dart
Normal file
|
@ -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');
|
||||||
|
}
|
12
samples/ffi/http/pubspec.yaml
Normal file
12
samples/ffi/http/pubspec.yaml
Normal file
|
@ -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
|
25
samples/ffi/http/test/http_test.dart
Normal file
25
samples/ffi/http/test/http_test.dart
Normal file
|
@ -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'));
|
||||||
|
});
|
||||||
|
}
|
|
@ -42,7 +42,6 @@ versiondir = ''
|
||||||
ignoredPaths = [
|
ignoredPaths = [
|
||||||
'buildtools/linux-x64/go',
|
'buildtools/linux-x64/go',
|
||||||
'buildtools/linux-x64/rust',
|
'buildtools/linux-x64/rust',
|
||||||
'samples',
|
|
||||||
'third_party/7zip',
|
'third_party/7zip',
|
||||||
'third_party/android_tools',
|
'third_party/android_tools',
|
||||||
'third_party/clang',
|
'third_party/clang',
|
||||||
|
|
Loading…
Reference in a new issue