mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 15:50:01 +00:00
[samples/ffi] Native resource lifetime management
Samples for managing native memory without finalizers. Design: go/dart-ffi-resource-lifetime Related issue: https://github.com/dart-lang/sdk/issues/35770 Change-Id: I2d0ac1acb65a78db9f57aea3dd5f25b4948ef6d6 Cq-Include-Trybots: luci.dart.try:vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64-try,app-kernel-linux-debug-x64-try,vm-kernel-linux-debug-ia32-try,vm-kernel-win-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-kernel-precomp-linux-debug-x64-try,vm-dartkb-linux-release-x64-abi-try,vm-kernel-precomp-android-release-arm64-try,vm-kernel-asan-linux-release-x64-try,vm-kernel-linux-release-simarm-try,vm-kernel-linux-release-simarm64-try,vm-kernel-precomp-android-release-arm_x64-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,vm-kernel-precomp-mac-release-simarm_x64-try,dart-sdk-linux-try,analyzer-analysis-server-linux-try,analyzer-linux-release-try,front-end-linux-release-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/123662 Commit-Queue: Daco Harkes <dacoharkes@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com> Reviewed-by: Erik Ernst <eernst@google.com>
This commit is contained in:
parent
1f791e0668
commit
4bd3166529
|
@ -2,6 +2,10 @@
|
|||
// 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#define DART_EXPORT extern "C" __declspec(dllexport)
|
||||
#else
|
||||
|
@ -16,3 +20,32 @@ DART_EXPORT int return42() {
|
|||
DART_EXPORT double timesFour(double d) {
|
||||
return d * 4.0;
|
||||
}
|
||||
|
||||
// Wrap memmove so we can easily find it on all platforms.
|
||||
//
|
||||
// We use this in our samples to illustrate resource lifetime management.
|
||||
DART_EXPORT void MemMove(void* destination, void* source, intptr_t num_bytes) {
|
||||
memmove(destination, source, num_bytes);
|
||||
}
|
||||
|
||||
// Some opaque struct.
|
||||
typedef struct {
|
||||
} some_resource;
|
||||
|
||||
DART_EXPORT some_resource* AllocateResource() {
|
||||
void* pointer = malloc(sizeof(int64_t));
|
||||
|
||||
// Dummy initialize.
|
||||
static_cast<int64_t*>(pointer)[0] = 10;
|
||||
|
||||
return static_cast<some_resource*>(pointer);
|
||||
}
|
||||
|
||||
DART_EXPORT void UseResource(some_resource* resource) {
|
||||
// Dummy change.
|
||||
reinterpret_cast<int64_t*>(resource)[0] += 10;
|
||||
}
|
||||
|
||||
DART_EXPORT void ReleaseResource(some_resource* resource) {
|
||||
free(resource);
|
||||
}
|
||||
|
|
194
samples/ffi/resource_management/pool.dart
Normal file
194
samples/ffi/resource_management/pool.dart
Normal file
|
@ -0,0 +1,194 @@
|
|||
// Copyright (c) 2019, 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.
|
||||
//
|
||||
// Explicit pool used for managing resources.
|
||||
|
||||
import "dart:async";
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:ffi/ffi.dart' as packageFfi;
|
||||
import 'package:ffi/ffi.dart' show Utf8;
|
||||
|
||||
/// Manages native resources.
|
||||
///
|
||||
/// Primary implementations are [Pool] and [Unmanaged].
|
||||
abstract class ResourceManager {
|
||||
/// Allocates memory on the native heap.
|
||||
///
|
||||
/// The native memory is under management by this [ResourceManager].
|
||||
///
|
||||
/// For POSIX-based systems, this uses malloc. On Windows, it uses HeapAlloc
|
||||
/// against the default public heap. Allocation of either element size or count
|
||||
/// of 0 is undefined.
|
||||
///
|
||||
/// Throws an ArgumentError on failure to allocate.
|
||||
Pointer<T> allocate<T extends NativeType>({int count: 1});
|
||||
}
|
||||
|
||||
/// Manages native resources.
|
||||
class Pool implements ResourceManager {
|
||||
/// Native memory under management by this [Pool].
|
||||
final List<Pointer<NativeType>> _managedMemoryPointers = [];
|
||||
|
||||
/// Callbacks for releasing native resources under management by this [Pool].
|
||||
final List<Function()> _managedResourceReleaseCallbacks = [];
|
||||
|
||||
/// Allocates memory on the native heap.
|
||||
///
|
||||
/// The native memory is under management by this [Pool].
|
||||
///
|
||||
/// For POSIX-based systems, this uses malloc. On Windows, it uses HeapAlloc
|
||||
/// against the default public heap. Allocation of either element size or count
|
||||
/// of 0 is undefined.
|
||||
///
|
||||
/// Throws an ArgumentError on failure to allocate.
|
||||
Pointer<T> allocate<T extends NativeType>({int count: 1}) {
|
||||
final p = Unmanaged().allocate<T>(count: count);
|
||||
_managedMemoryPointers.add(p);
|
||||
return p;
|
||||
}
|
||||
|
||||
/// Registers [resource] in this pool.
|
||||
///
|
||||
/// Executes [releaseCallback] on [releaseAll].
|
||||
T using<T>(T resource, Function(T) releaseCallback) {
|
||||
_managedResourceReleaseCallbacks.add(() => releaseCallback(resource));
|
||||
return resource;
|
||||
}
|
||||
|
||||
/// Registers [releaseResourceCallback] to be executed on [releaseAll].
|
||||
void onReleaseAll(Function() releaseResourceCallback) {
|
||||
_managedResourceReleaseCallbacks.add(releaseResourceCallback);
|
||||
}
|
||||
|
||||
/// Releases all resources that this [Pool] manages.
|
||||
void releaseAll() {
|
||||
for (final c in _managedResourceReleaseCallbacks) {
|
||||
c();
|
||||
}
|
||||
_managedResourceReleaseCallbacks.clear();
|
||||
for (final p in _managedMemoryPointers) {
|
||||
Unmanaged().free(p);
|
||||
}
|
||||
_managedMemoryPointers.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [Pool] to manage native resources.
|
||||
///
|
||||
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
|
||||
R using<R>(R Function(Pool) f) {
|
||||
final p = Pool();
|
||||
try {
|
||||
return f(p);
|
||||
} finally {
|
||||
p.releaseAll();
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a zoned [Pool] to manage native resources.
|
||||
///
|
||||
/// Pool is availabe through [currentPool].
|
||||
///
|
||||
/// Please note that all throws are caught and packaged in [RethrownError].
|
||||
///
|
||||
/// If the isolate is shut down, through `Isolate.kill()`, resources are _not_ cleaned up.
|
||||
R usePool<R>(R Function() f) {
|
||||
final p = Pool();
|
||||
try {
|
||||
return runZoned(() => f(),
|
||||
zoneValues: {#_pool: p},
|
||||
onError: (error, st) => throw RethrownError(error, st));
|
||||
} finally {
|
||||
p.releaseAll();
|
||||
}
|
||||
}
|
||||
|
||||
/// The [Pool] in the current zone.
|
||||
Pool get currentPool => Zone.current[#_pool];
|
||||
|
||||
class RethrownError {
|
||||
dynamic original;
|
||||
StackTrace originalStackTrace;
|
||||
RethrownError(this.original, this.originalStackTrace);
|
||||
toString() => """RethrownError(${original})
|
||||
${originalStackTrace}""";
|
||||
}
|
||||
|
||||
/// Does not manage it's resources.
|
||||
class Unmanaged implements ResourceManager {
|
||||
/// Allocates memory on the native heap.
|
||||
///
|
||||
/// For POSIX-based systems, this uses malloc. On Windows, it uses HeapAlloc
|
||||
/// against the default public heap. Allocation of either element size or count
|
||||
/// of 0 is undefined.
|
||||
///
|
||||
/// Throws an ArgumentError on failure to allocate.
|
||||
Pointer<T> allocate<T extends NativeType>({int count = 1}) =>
|
||||
packageFfi.allocate(count: count);
|
||||
|
||||
/// Releases memory on the native heap.
|
||||
///
|
||||
/// For POSIX-based systems, this uses free. On Windows, it uses HeapFree
|
||||
/// against the default public heap. It may only be used against pointers
|
||||
/// allocated in a manner equivalent to [allocate].
|
||||
///
|
||||
/// Throws an ArgumentError on failure to free.
|
||||
///
|
||||
void free(Pointer pointer) => packageFfi.free(pointer);
|
||||
}
|
||||
|
||||
/// Does not manage it's resources.
|
||||
final Unmanaged unmanaged = Unmanaged();
|
||||
|
||||
extension Utf8InPool on String {
|
||||
/// Convert a [String] to a Utf8-encoded null-terminated C string.
|
||||
///
|
||||
/// If 'string' contains NULL bytes, the converted string will be truncated
|
||||
/// prematurely. Unpaired surrogate code points in [string] will be preserved
|
||||
/// in the UTF-8 encoded result. See [Utf8Encoder] for details on encoding.
|
||||
///
|
||||
/// Returns a malloc-allocated pointer to the result.
|
||||
///
|
||||
/// The memory is managed by the [Pool] passed in as [pool].
|
||||
Pointer<Utf8> toUtf8(ResourceManager pool) {
|
||||
final units = utf8.encode(this);
|
||||
final Pointer<Uint8> result = pool.allocate<Uint8>(count: units.length + 1);
|
||||
final Uint8List nativeString = result.asTypedList(units.length + 1);
|
||||
nativeString.setAll(0, units);
|
||||
nativeString[units.length] = 0;
|
||||
return result.cast();
|
||||
}
|
||||
}
|
||||
|
||||
extension Utf8Helpers on Pointer<Utf8> {
|
||||
/// Returns the length of a null-terminated string -- the number of (one-byte)
|
||||
/// characters before the first null byte.
|
||||
int strlen() {
|
||||
final Pointer<Uint8> array = this.cast<Uint8>();
|
||||
final Uint8List nativeString = array.asTypedList(_maxSize);
|
||||
return nativeString.indexWhere((char) => char == 0);
|
||||
}
|
||||
|
||||
/// Creates a [String] containing the characters UTF-8 encoded in [this].
|
||||
///
|
||||
/// [this] must be a zero-terminated byte sequence of valid UTF-8
|
||||
/// encodings of Unicode code points. It may also contain UTF-8 encodings of
|
||||
/// unpaired surrogate code points, which is not otherwise valid UTF-8, but
|
||||
/// which may be created when encoding a Dart string containing an unpaired
|
||||
/// surrogate. See [Utf8Decoder] for details on decoding.
|
||||
///
|
||||
/// Returns a Dart string containing the decoded code points.
|
||||
String contents() {
|
||||
final int length = strlen();
|
||||
return utf8.decode(Uint8List.view(
|
||||
this.cast<Uint8>().asTypedList(length).buffer, 0, length));
|
||||
}
|
||||
}
|
||||
|
||||
const int _kMaxSmi64 = (1 << 62) - 1;
|
||||
const int _kMaxSmi32 = (1 << 30) - 1;
|
||||
final int _maxSize = sizeOf<IntPtr>() == 8 ? _kMaxSmi64 : _kMaxSmi32;
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) 2019, 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 illustrating resources are not cleaned up when isolate is shutdown.
|
||||
|
||||
import 'dart:io';
|
||||
import "dart:isolate";
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'pool.dart';
|
||||
import '../dylib_utils.dart';
|
||||
|
||||
void main() {
|
||||
final receiveFromHelper = ReceivePort();
|
||||
|
||||
Isolate.spawn(helperIsolateMain, receiveFromHelper.sendPort)
|
||||
.then((helperIsolate) {
|
||||
helperIsolate.addOnExitListener(
|
||||
receiveFromHelper.sendPort,
|
||||
);
|
||||
print("Main: Helper started.");
|
||||
Pointer<SomeResource> resource;
|
||||
receiveFromHelper.listen((message) {
|
||||
if (message is int) {
|
||||
resource = Pointer<SomeResource>.fromAddress(message);
|
||||
print("Main: Received resource from helper: $resource.");
|
||||
print("Main: Shutting down helper.");
|
||||
helperIsolate.kill(priority: Isolate.immediate);
|
||||
} else {
|
||||
// Isolate kill message.
|
||||
Expect.isNull(message);
|
||||
print("Main: Helper is shut down.");
|
||||
print(
|
||||
"Main: Trying to use resource after isolate that was supposed to free it was shut down.");
|
||||
useResource(resource);
|
||||
print("Main: Releasing resource manually.");
|
||||
releaseResource(resource);
|
||||
print("Main: Shutting down receive port, end of main.");
|
||||
receiveFromHelper.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// If set to `false`, this sample can segfault due to use after free and
|
||||
/// double free.
|
||||
const keepHelperIsolateAlive = true;
|
||||
|
||||
void helperIsolateMain(SendPort sendToMain) {
|
||||
using((Pool pool) {
|
||||
final resource = pool.using(allocateResource(), releaseResource);
|
||||
pool.onReleaseAll(() {
|
||||
// Will only run print if [keepHelperIsolateAlive] is false.
|
||||
print("Helper: Releasing all resources.");
|
||||
});
|
||||
print("Helper: Resource allocated.");
|
||||
useResource(resource);
|
||||
print("Helper: Sending resource to main: $resource.");
|
||||
sendToMain.send(resource.address);
|
||||
print("Helper: Going to sleep.");
|
||||
if (keepHelperIsolateAlive) {
|
||||
while (true) {
|
||||
sleep(Duration(seconds: 1));
|
||||
print("Helper: sleeping.");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final ffiTestDynamicLibrary =
|
||||
dlopenPlatformSpecific("ffi_test_dynamic_library");
|
||||
|
||||
final allocateResource = ffiTestDynamicLibrary.lookupFunction<
|
||||
Pointer<SomeResource> Function(),
|
||||
Pointer<SomeResource> Function()>("AllocateResource");
|
||||
|
||||
final useResource = ffiTestDynamicLibrary.lookupFunction<
|
||||
Void Function(Pointer<SomeResource>),
|
||||
void Function(Pointer<SomeResource>)>("UseResource");
|
||||
|
||||
final releaseResource = ffiTestDynamicLibrary.lookupFunction<
|
||||
Void Function(Pointer<SomeResource>),
|
||||
void Function(Pointer<SomeResource>)>("ReleaseResource");
|
||||
|
||||
/// Represents some opaque resource being managed by a library.
|
||||
class SomeResource extends Struct {}
|
113
samples/ffi/resource_management/pool_sample.dart
Normal file
113
samples/ffi/resource_management/pool_sample.dart
Normal file
|
@ -0,0 +1,113 @@
|
|||
// Copyright (c) 2019, 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 illustrating resource management with an explicit pool.
|
||||
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'pool.dart';
|
||||
import '../dylib_utils.dart';
|
||||
|
||||
main() {
|
||||
final ffiTestDynamicLibrary =
|
||||
dlopenPlatformSpecific("ffi_test_dynamic_library");
|
||||
|
||||
final MemMove = ffiTestDynamicLibrary.lookupFunction<
|
||||
Void Function(Pointer<Void>, Pointer<Void>, IntPtr),
|
||||
void Function(Pointer<Void>, Pointer<Void>, int)>("MemMove");
|
||||
|
||||
// To ensure resources are freed, wrap them in a [using] call.
|
||||
using((Pool pool) {
|
||||
final p = pool.allocate<Int64>(count: 2);
|
||||
p[0] = 24;
|
||||
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
|
||||
print(p[1]);
|
||||
Expect.equals(24, p[1]);
|
||||
});
|
||||
|
||||
// Resources are freed also when abnormal control flow occurs.
|
||||
try {
|
||||
using((Pool pool) {
|
||||
final p = pool.allocate<Int64>(count: 2);
|
||||
p[0] = 25;
|
||||
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), 8);
|
||||
print(p[1]);
|
||||
Expect.equals(25, p[1]);
|
||||
throw Exception("Some random exception");
|
||||
});
|
||||
// `free(p)` has been called.
|
||||
} on Exception catch (e) {
|
||||
print("Caught exception: $e");
|
||||
}
|
||||
|
||||
// In a pool multiple resources can be allocated, which will all be freed
|
||||
// at the end of the scope.
|
||||
using((Pool pool) {
|
||||
final p = pool.allocate<Int64>(count: 2);
|
||||
final p2 = pool.allocate<Int64>(count: 2);
|
||||
p[0] = 1;
|
||||
p[1] = 2;
|
||||
MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
|
||||
Expect.equals(1, p2[0]);
|
||||
Expect.equals(2, p2[1]);
|
||||
});
|
||||
|
||||
// If the resource allocation happens in a different scope, then one either
|
||||
// needs to pass the pool to that scope.
|
||||
f1(Pool pool) {
|
||||
return pool.allocate<Int64>(count: 2);
|
||||
}
|
||||
|
||||
using((Pool pool) {
|
||||
final p = f1(pool);
|
||||
final p2 = f1(pool);
|
||||
p[0] = 1;
|
||||
p[1] = 2;
|
||||
MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
|
||||
Expect.equals(1, p2[0]);
|
||||
Expect.equals(2, p2[1]);
|
||||
});
|
||||
|
||||
// Using Strings.
|
||||
using((Pool pool) {
|
||||
final p = "Hello world!".toUtf8(pool);
|
||||
print(p.contents());
|
||||
});
|
||||
|
||||
final allocateResource = ffiTestDynamicLibrary.lookupFunction<
|
||||
Pointer<SomeResource> Function(),
|
||||
Pointer<SomeResource> Function()>("AllocateResource");
|
||||
|
||||
final useResource = ffiTestDynamicLibrary.lookupFunction<
|
||||
Void Function(Pointer<SomeResource>),
|
||||
void Function(Pointer<SomeResource>)>("UseResource");
|
||||
|
||||
final releaseResource = ffiTestDynamicLibrary.lookupFunction<
|
||||
Void Function(Pointer<SomeResource>),
|
||||
void Function(Pointer<SomeResource>)>("ReleaseResource");
|
||||
|
||||
// Using an FFI call to release a resource.
|
||||
using((Pool pool) {
|
||||
final r = pool.using(allocateResource(), releaseResource);
|
||||
useResource(r);
|
||||
});
|
||||
|
||||
// Using an FFI call to release a resource with abnormal control flow.
|
||||
try {
|
||||
using((Pool pool) {
|
||||
final r = pool.using(allocateResource(), releaseResource);
|
||||
useResource(r);
|
||||
|
||||
throw Exception("Some random exception");
|
||||
});
|
||||
// Resource has been freed.
|
||||
} on Exception catch (e) {
|
||||
print("Caught exception: $e");
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents some opaque resource being managed by a library.
|
||||
class SomeResource extends Struct {}
|
114
samples/ffi/resource_management/pool_zoned_sample.dart
Normal file
114
samples/ffi/resource_management/pool_zoned_sample.dart
Normal file
|
@ -0,0 +1,114 @@
|
|||
// Copyright (c) 2019, 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 illustrating resource management with an implicit pool in the zone.
|
||||
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'pool.dart';
|
||||
import '../dylib_utils.dart';
|
||||
|
||||
main() {
|
||||
final ffiTestDynamicLibrary =
|
||||
dlopenPlatformSpecific("ffi_test_dynamic_library");
|
||||
|
||||
final MemMove = ffiTestDynamicLibrary.lookupFunction<
|
||||
Void Function(Pointer<Void>, Pointer<Void>, IntPtr),
|
||||
void Function(Pointer<Void>, Pointer<Void>, int)>("MemMove");
|
||||
|
||||
// To ensure resources are freed, wrap them in a [using] call.
|
||||
usePool(() {
|
||||
final p = currentPool.allocate<Int64>(count: 2);
|
||||
p[0] = 24;
|
||||
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
|
||||
print(p[1]);
|
||||
Expect.equals(24, p[1]);
|
||||
});
|
||||
|
||||
// Resources are freed also when abnormal control flow occurs.
|
||||
try {
|
||||
usePool(() {
|
||||
final p = currentPool.allocate<Int64>(count: 2);
|
||||
p[0] = 25;
|
||||
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), 8);
|
||||
print(p[1]);
|
||||
Expect.equals(25, p[1]);
|
||||
throw Exception("Some random exception");
|
||||
});
|
||||
} on RethrownError catch (e) {
|
||||
// Note that exceptions are wrapped when using zones.
|
||||
print("Caught exception: ${e.original}");
|
||||
}
|
||||
|
||||
// In a pool multiple resources can be allocated, which will all be freed
|
||||
// at the end of the scope.
|
||||
usePool(() {
|
||||
final p = currentPool.allocate<Int64>(count: 2);
|
||||
final p2 = currentPool.allocate<Int64>(count: 2);
|
||||
p[0] = 1;
|
||||
p[1] = 2;
|
||||
MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
|
||||
Expect.equals(1, p2[0]);
|
||||
Expect.equals(2, p2[1]);
|
||||
});
|
||||
|
||||
// If the resource allocation happens in a different scope, it is in the
|
||||
// same zone, so it's lifetime is automatically managed by the pool.
|
||||
f1() {
|
||||
return currentPool.allocate<Int64>(count: 2);
|
||||
}
|
||||
|
||||
usePool(() {
|
||||
final p = f1();
|
||||
final p2 = f1();
|
||||
p[0] = 1;
|
||||
p[1] = 2;
|
||||
MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
|
||||
Expect.equals(1, p2[0]);
|
||||
Expect.equals(2, p2[1]);
|
||||
});
|
||||
|
||||
// Using Strings.
|
||||
usePool(() {
|
||||
final p = "Hello world!".toUtf8(currentPool);
|
||||
print(p.contents());
|
||||
});
|
||||
|
||||
final allocateResource = ffiTestDynamicLibrary.lookupFunction<
|
||||
Pointer<SomeResource> Function(),
|
||||
Pointer<SomeResource> Function()>("AllocateResource");
|
||||
|
||||
final useResource = ffiTestDynamicLibrary.lookupFunction<
|
||||
Void Function(Pointer<SomeResource>),
|
||||
void Function(Pointer<SomeResource>)>("UseResource");
|
||||
|
||||
final releaseResource = ffiTestDynamicLibrary.lookupFunction<
|
||||
Void Function(Pointer<SomeResource>),
|
||||
void Function(Pointer<SomeResource>)>("ReleaseResource");
|
||||
|
||||
// Using an FFI call to release a resource.
|
||||
usePool(() {
|
||||
final r = currentPool.using(allocateResource(), releaseResource);
|
||||
useResource(r);
|
||||
});
|
||||
|
||||
// Using an FFI call to release a resource with abnormal control flow.
|
||||
try {
|
||||
usePool(() {
|
||||
final r = currentPool.using(allocateResource(), releaseResource);
|
||||
useResource(r);
|
||||
|
||||
throw Exception("Some random exception");
|
||||
});
|
||||
// Resource has been freed.
|
||||
} on RethrownError catch (e) {
|
||||
// Note that exceptions are wrapped when using zones.
|
||||
print("Caught exception: ${e.original}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents some opaque resource being managed by a library.
|
||||
class SomeResource extends Struct {}
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) 2019, 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.
|
||||
//
|
||||
// This file exercises the sample files so that they are tested.
|
||||
//
|
||||
// SharedObjects=ffi_test_dynamic_library ffi_test_functions
|
||||
|
||||
import 'pool_isolate_shutdown_sample.dart' as pool_isolate;
|
||||
import 'pool_sample.dart' as pool;
|
||||
import 'pool_zoned_sample.dart' as pool_zoned;
|
||||
import 'unmanaged_sample.dart' as unmanaged;
|
||||
|
||||
main() {
|
||||
pool_isolate.main();
|
||||
pool.main();
|
||||
pool_zoned.main();
|
||||
unmanaged.main();
|
||||
}
|
36
samples/ffi/resource_management/unmanaged_sample.dart
Normal file
36
samples/ffi/resource_management/unmanaged_sample.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) 2019, 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 illustrating manual resource management, not advised.
|
||||
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
|
||||
import 'pool.dart';
|
||||
import '../dylib_utils.dart';
|
||||
|
||||
main() {
|
||||
final ffiTestDynamicLibrary =
|
||||
dlopenPlatformSpecific("ffi_test_dynamic_library");
|
||||
|
||||
final MemMove = ffiTestDynamicLibrary.lookupFunction<
|
||||
Void Function(Pointer<Void>, Pointer<Void>, IntPtr),
|
||||
void Function(Pointer<Void>, Pointer<Void>, int)>("MemMove");
|
||||
|
||||
// To ensure resources are freed, call free manually.
|
||||
//
|
||||
// For automatic management use a Pool.
|
||||
final p = unmanaged.allocate<Int64>(count: 2);
|
||||
p[0] = 24;
|
||||
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
|
||||
print(p[1]);
|
||||
Expect.equals(24, p[1]);
|
||||
unmanaged.free(p);
|
||||
|
||||
// Using Strings.
|
||||
final p2 = "Hello world!".toUtf8(unmanaged);
|
||||
print(p2.contents());
|
||||
unmanaged.free(p2);
|
||||
}
|
|
@ -5,6 +5,9 @@
|
|||
[ $arch == arm ]
|
||||
sample_extension/test/*: Skip # Issue 14705
|
||||
|
||||
[ $builder_tag == asan ]
|
||||
ffi/samples_test: SkipByDesign # FFI skips, see ffi.status
|
||||
|
||||
[ $builder_tag == optimization_counter_threshold ]
|
||||
sample_extension/test/sample_extension_app_snapshot_test: SkipByDesign # This test is too slow for testing with low optimization counter threshold.
|
||||
|
||||
|
@ -17,7 +20,8 @@ sample_extension/test/sample_extension_app_snapshot_test: Pass, RuntimeError # I
|
|||
[ $compiler == none && $runtime == vm && $system == fuchsia ]
|
||||
*: Skip # Not yet triaged.
|
||||
|
||||
[ $arch == simarm || $arch == simarm64 || $builder_tag == asan ]
|
||||
[ $arch == simarm || $arch == simarm64 ]
|
||||
ffi/resource_management/resource_management_test: SkipByDesign
|
||||
ffi/samples_test: SkipByDesign # FFI skips, see ffi.status
|
||||
|
||||
[ $arch != x64 || $compiler != dartk || $system != linux || $hot_reload || $hot_reload_rollback ]
|
||||
|
|
Loading…
Reference in a new issue