[vm/ffi] Migrate samples(_2)/ffi to CallocAllocator

This CL does not yet roll `package:ffi` to use `Allocator`, because that
breaks the checked in Dart in Flutter in g3. Instead, this copies
`_CallocAllocator` from `package:ffi` into the samples.

New API landed in: https://dart-review.googlesource.com/c/sdk/+/177705

Issue: https://github.com/dart-lang/sdk/issues/44621
Issue: https://github.com/dart-lang/sdk/issues/38721

Change-Id: I83da349c2e52d7f079aa1569b4726318fee24c9d
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/177706
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Aske Simon Christensen <askesc@google.com>
This commit is contained in:
Daco Harkes 2021-01-14 13:57:03 +00:00 committed by commit-bot@chromium.org
parent 978b838461
commit d37e833e23
36 changed files with 822 additions and 396 deletions

109
samples/ffi/calloc.dart Normal file
View file

@ -0,0 +1,109 @@
// Copyright (c) 2021, 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.
// TODO(https://dartbug.com/44621): Remove this copy when package:ffi can be
// rolled. We need to wait until the `Allocator` interface has rolled into
// Flutter.
import 'dart:ffi';
import 'dart:io';
final DynamicLibrary stdlib = Platform.isWindows
? DynamicLibrary.open('kernel32.dll')
: DynamicLibrary.process();
typedef PosixCallocNative = Pointer Function(IntPtr num, IntPtr size);
typedef PosixCalloc = Pointer Function(int num, int size);
final PosixCalloc posixCalloc =
stdlib.lookupFunction<PosixCallocNative, PosixCalloc>('calloc');
typedef PosixFreeNative = Void Function(Pointer);
typedef PosixFree = void Function(Pointer);
final PosixFree posixFree =
stdlib.lookupFunction<PosixFreeNative, PosixFree>('free');
typedef WinGetProcessHeapFn = Pointer Function();
final WinGetProcessHeapFn winGetProcessHeap = stdlib
.lookupFunction<WinGetProcessHeapFn, WinGetProcessHeapFn>('GetProcessHeap');
final Pointer processHeap = winGetProcessHeap();
typedef WinHeapAllocNative = Pointer Function(Pointer, Uint32, IntPtr);
typedef WinHeapAlloc = Pointer Function(Pointer, int, int);
final WinHeapAlloc winHeapAlloc =
stdlib.lookupFunction<WinHeapAllocNative, WinHeapAlloc>('HeapAlloc');
typedef WinHeapFreeNative = Int32 Function(
Pointer heap, Uint32 flags, Pointer memory);
typedef WinHeapFree = int Function(Pointer heap, int flags, Pointer memory);
final WinHeapFree winHeapFree =
stdlib.lookupFunction<WinHeapFreeNative, WinHeapFree>('HeapFree');
const int HEAP_ZERO_MEMORY = 8;
/// Manages memory on the native heap.
///
/// Initializes newly allocated memory to zero.
///
/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses
/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default
/// public heap.
class _CallocAllocator implements Allocator {
const _CallocAllocator();
/// Allocates [byteCount] bytes of zero-initialized of memory on the native
/// heap.
///
/// For POSIX-based systems, this uses `calloc`. On Windows, it uses
/// `HeapAlloc` against the default public heap.
///
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
/// satisfied.
// TODO: Stop ignoring alignment if it's large, for example for SSE data.
@override
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
Pointer<T> result;
if (Platform.isWindows) {
result = winHeapAlloc(processHeap, /*flags=*/ HEAP_ZERO_MEMORY, byteCount)
.cast();
} else {
result = posixCalloc(byteCount, 1).cast();
}
if (result.address == 0) {
throw ArgumentError('Could not allocate $byteCount bytes.');
}
return result;
}
/// Releases memory allocated 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] if the memory pointed to by [pointer] cannot be
/// freed.
///
// TODO(dartbug.com/36855): Once we have a ffi.Bool type we can use it instead
// of testing the return integer to be non-zero.
@override
void free(Pointer pointer) {
if (Platform.isWindows) {
if (winHeapFree(processHeap, /*flags=*/ 0, pointer) == 0) {
throw ArgumentError('Could not free $pointer.');
}
} else {
posixFree(pointer);
}
}
}
/// Manages memory on the native heap.
///
/// Initializes newly allocated memory to zero. Use [malloc] for unintialized
/// memory allocation.
///
/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses
/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default
/// public heap.
const Allocator calloc = _CallocAllocator();

View file

@ -16,8 +16,9 @@ class Coordinate extends Struct {
external Pointer<Coordinate> next;
factory Coordinate.allocate(double x, double y, Pointer<Coordinate> next) {
return allocate<Coordinate>().ref
factory Coordinate.allocate(
Allocator allocator, double x, double y, Pointer<Coordinate> next) {
return allocator<Coordinate>().ref
..x = x
..y = y
..next = next;

View file

@ -5,48 +5,36 @@
// 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;
import 'package:ffi/ffi.dart';
/// Manages native resources.
import '../calloc.dart';
/// Keeps track of all allocated memory and frees all allocated memory on
/// [releaseAll].
///
/// 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});
}
/// Wraps an [Allocator] to do the actual allocation and freeing.
class Pool implements Allocator {
/// The [Allocator] used for allocation and freeing.
final Allocator _wrappedAllocator;
Pool(this._wrappedAllocator);
/// 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.
/// Allocates memory on the native heap by using the allocator supplied to
/// the constructor.
///
/// 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);
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
/// satisfied.
@override
Pointer<T> allocate<T extends NativeType>(int numBytes, {int? alignment}) {
final p = _wrappedAllocator.allocate<T>(numBytes, alignment: alignment);
_managedMemoryPointers.add(p);
return p;
}
@ -71,17 +59,21 @@ class Pool implements ResourceManager {
}
_managedResourceReleaseCallbacks.clear();
for (final p in _managedMemoryPointers) {
Unmanaged().free(p);
_wrappedAllocator.free(p);
}
_managedMemoryPointers.clear();
}
@override
void free(Pointer<NativeType> pointer) => throw UnsupportedError(
"Individually freeing Pool allocated memory is not allowed");
}
/// 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();
R using<R>(R Function(Pool) f, [Allocator wrappedAllocator = calloc]) {
final p = Pool(wrappedAllocator);
try {
return f(p);
} finally {
@ -96,8 +88,8 @@ R using<R>(R Function(Pool) f) {
/// 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();
R usePool<R>(R Function() f, [Allocator wrappedAllocator = calloc]) {
final p = Pool(wrappedAllocator);
try {
return runZoned(() => f(),
zoneValues: {#_pool: p},
@ -117,78 +109,3 @@ class RethrownError {
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;

View file

@ -9,6 +9,7 @@ import 'dart:ffi';
import 'package:expect/expect.dart';
import 'pool.dart';
import 'utf8_helpers.dart';
import '../dylib_utils.dart';
main() {
@ -21,7 +22,7 @@ main() {
// To ensure resources are freed, wrap them in a [using] call.
using((Pool pool) {
final p = pool.allocate<Int64>(count: 2);
final p = pool<Int64>(2);
p[0] = 24;
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
print(p[1]);
@ -31,14 +32,14 @@ main() {
// Resources are freed also when abnormal control flow occurs.
try {
using((Pool pool) {
final p = pool.allocate<Int64>(count: 2);
final p = pool<Int64>(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.
// `calloc.free(p)` has been called.
} on Exception catch (e) {
print("Caught exception: $e");
}
@ -46,8 +47,8 @@ main() {
// 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);
final p = pool<Int64>(2);
final p2 = pool<Int64>(2);
p[0] = 1;
p[1] = 2;
MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
@ -58,7 +59,7 @@ main() {
// 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);
return pool<Int64>(2);
}
using((Pool pool) {

View file

@ -9,6 +9,7 @@ import 'dart:ffi';
import 'package:expect/expect.dart';
import 'pool.dart';
import 'utf8_helpers.dart';
import '../dylib_utils.dart';
main() {
@ -21,7 +22,7 @@ main() {
// To ensure resources are freed, wrap them in a [using] call.
usePool(() {
final p = currentPool.allocate<Int64>(count: 2);
final p = currentPool<Int64>(2);
p[0] = 24;
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
print(p[1]);
@ -31,7 +32,7 @@ main() {
// Resources are freed also when abnormal control flow occurs.
try {
usePool(() {
final p = currentPool.allocate<Int64>(count: 2);
final p = currentPool<Int64>(2);
p[0] = 25;
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), 8);
print(p[1]);
@ -46,8 +47,8 @@ main() {
// 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);
final p = currentPool<Int64>(2);
final p2 = currentPool<Int64>(2);
p[0] = 1;
p[1] = 2;
MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
@ -58,7 +59,7 @@ main() {
// 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);
return currentPool<Int64>(2);
}
usePool(() {

View file

@ -7,30 +7,32 @@
import 'dart:ffi';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
import 'pool.dart';
import 'utf8_helpers.dart';
import '../calloc.dart';
import '../dylib_utils.dart';
main() {
final ffiTestDynamicLibrary =
dlopenPlatformSpecific("ffi_test_dynamic_library");
final MemMove = ffiTestDynamicLibrary.lookupFunction<
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);
final p = calloc<Int64>(2);
p[0] = 24;
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
memMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
print(p[1]);
Expect.equals(24, p[1]);
unmanaged.free(p);
calloc.free(p);
// Using Strings.
final p2 = "Hello world!".toUtf8(unmanaged);
final p2 = "Hello world!".toUtf8(calloc);
print(p2.contents());
unmanaged.free(p2);
calloc.free(p2);
}

View file

@ -0,0 +1,53 @@
// Copyright (c) 2021, 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:convert';
import 'dart:ffi';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
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. See [Utf8Encoder] for details on encoding.
///
/// Returns a [allocator]-allocated pointer to the result.
Pointer<Utf8> toUtf8(Allocator allocator) {
final units = utf8.encode(this);
final Pointer<Uint8> result = allocator<Uint8>(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> {
/// The length of a null-terminated string the number of (one-byte)
/// characters before the first null byte.
int get 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. 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;

View file

@ -7,6 +7,8 @@ import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:expect/expect.dart';
import 'calloc.dart';
/// typedef struct {
/// unsigned int bold : 1;
/// unsigned int underline : 2;
@ -93,7 +95,7 @@ extension IntBitField on int {
}
main() {
final p = allocate<ScreenCellAttrs>(count: 3);
final p = calloc<ScreenCellAttrs>(3);
// Zeroes out all fields.
p.ref.bits = 0;
@ -119,5 +121,5 @@ main() {
// A check for automated testing.
Expect.equals(1933, p.ref.bits);
free(p);
calloc.free(p);
}

View file

@ -5,29 +5,31 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'calloc.dart';
main() {
print('start main');
{
// Basic operation: allocate, get, set, and free.
Pointer<Int64> p = allocate();
Pointer<Int64> p = calloc();
p.value = 42;
int pValue = p.value;
print('${p.runtimeType} value: ${pValue}');
free(p);
calloc.free(p);
}
{
// Undefined behavior before set.
Pointer<Int64> p = allocate();
Pointer<Int64> p = calloc();
int pValue = p.value;
print('If not set, returns garbage: ${pValue}');
free(p);
calloc.free(p);
}
{
// Pointers can be created from an address.
Pointer<Int64> pHelper = allocate();
Pointer<Int64> pHelper = calloc();
pHelper.value = 1337;
int address = pHelper.address;
@ -36,13 +38,13 @@ main() {
Pointer<Int64> p = Pointer.fromAddress(address);
print('${p.runtimeType} value: ${p.value}');
free(pHelper);
calloc.free(pHelper);
}
{
// Address is zeroed out after free.
Pointer<Int64> p = allocate();
free(p);
Pointer<Int64> p = calloc();
calloc.free(p);
print('After free, address is zero: ${p.address}');
}
@ -50,13 +52,13 @@ main() {
// Allocating too much throws an exception.
try {
int maxMint = 9223372036854775807; // 2^63 - 1
allocate<Int64>(count: maxMint);
calloc<Int64>(maxMint);
} on Error {
print('Expected exception on allocating too much');
}
try {
int maxInt1_8 = 1152921504606846975; // 2^60 -1
allocate<Int64>(count: maxInt1_8);
calloc<Int64>(maxInt1_8);
} on Error {
print('Expected exception on allocating too much');
}
@ -65,7 +67,7 @@ main() {
{
// Pointers can be cast into another type,
// resulting in the corresponding bits read.
Pointer<Int64> p1 = allocate();
Pointer<Int64> p1 = calloc();
p1.value = 9223372036854775807; // 2^63 - 1
Pointer<Int32> p2 = p1.cast();
@ -74,61 +76,61 @@ main() {
Pointer<Int32> p3 = p2.elementAt(1);
print('${p3.runtimeType} value: ${p3.value}'); // 2^31 - 1
free(p1);
calloc.free(p1);
}
{
// Data can be tightly packed in memory.
Pointer<Int8> p = allocate(count: 8);
Pointer<Int8> p = calloc(8);
for (var i in [0, 1, 2, 3, 4, 5, 6, 7]) {
p.elementAt(i).value = i * 3;
}
for (var i in [0, 1, 2, 3, 4, 5, 6, 7]) {
print('p.elementAt($i) value: ${p.elementAt(i).value}');
}
free(p);
calloc.free(p);
}
{
// Values that don't fit are truncated.
Pointer<Int32> p11 = allocate();
Pointer<Int32> p11 = calloc();
p11.value = 9223372036854775807;
print(p11);
free(p11);
calloc.free(p11);
}
{
// Doubles.
Pointer<Double> p = allocate();
Pointer<Double> p = calloc();
p.value = 3.14159265359;
print('${p.runtimeType} value: ${p.value}');
p.value = 3.14;
print('${p.runtimeType} value: ${p.value}');
free(p);
calloc.free(p);
}
{
// Floats.
Pointer<Float> p = allocate();
Pointer<Float> p = calloc();
p.value = 3.14159265359;
print('${p.runtimeType} value: ${p.value}');
p.value = 3.14;
print('${p.runtimeType} value: ${p.value}');
free(p);
calloc.free(p);
}
{
// IntPtr varies in size based on whether the platform is 32 or 64 bit.
// Addresses of pointers fit in this size.
Pointer<IntPtr> p = allocate();
Pointer<IntPtr> p = calloc();
int p14addr = p.address;
p.value = p14addr;
int pValue = p.value;
print('${p.runtimeType} value: ${pValue}');
free(p);
calloc.free(p);
}
{
@ -136,19 +138,19 @@ main() {
// The size of the element it is pointing to is undefined,
// they cannot be allocated, read, or written.
Pointer<IntPtr> p1 = allocate();
Pointer<IntPtr> p1 = calloc();
Pointer<Void> p2 = p1.cast();
print('${p2.runtimeType} address: ${p2.address}');
free(p1);
calloc.free(p1);
}
{
// Pointer to a pointer to something.
Pointer<Int16> pHelper = allocate();
Pointer<Int16> pHelper = calloc();
pHelper.value = 17;
Pointer<Pointer<Int16>> p = allocate();
Pointer<Pointer<Int16>> p = calloc();
// Storing into a pointer pointer automatically unboxes.
p.value = pHelper;
@ -160,31 +162,31 @@ main() {
int pValue = p.value.value;
print('${p.runtimeType} value\'s value: ${pValue}');
free(p);
free(pHelper);
calloc.free(p);
calloc.free(pHelper);
}
{
// The pointer to pointer types must match up.
Pointer<Int8> pHelper = allocate();
Pointer<Int8> pHelper = calloc();
pHelper.value = 123;
Pointer<Pointer<Int16>> p = allocate();
Pointer<Pointer<Int16>> p = calloc();
// Trying to store `pHelper` into `p.val` would result in a type mismatch.
free(pHelper);
free(p);
calloc.free(pHelper);
calloc.free(p);
}
{
// `nullptr` points to address 0 in c++.
Pointer<Pointer<Int8>> pointerToPointer = allocate();
Pointer<Pointer<Int8>> pointerToPointer = calloc();
Pointer<Int8> value = nullptr;
pointerToPointer.value = value;
value = pointerToPointer.value;
print("Loading a pointer to the 0 address is null: ${value}");
free(pointerToPointer);
calloc.free(pointerToPointer);
}
{
@ -205,7 +207,7 @@ main() {
head.value = value;
return;
}
Pointer<IntPtr> next = allocate<IntPtr>();
Pointer<IntPtr> next = calloc<IntPtr>();
head.value = next.address;
createChain(next, length - 1, value);
}
@ -220,7 +222,7 @@ main() {
void freeChain(Pointer<IntPtr> head, int length) {
Pointer<IntPtr> next = Pointer.fromAddress(head.value);
free(head);
calloc.free(head);
if (length == 0) {
return;
}
@ -228,7 +230,7 @@ main() {
}
int length = 10;
Pointer<IntPtr> head = allocate();
Pointer<IntPtr> head = calloc();
createChain(head, length, 512);
int tailValue = getChainValue(head, length);
print('tailValue: ${tailValue}');

View file

@ -6,6 +6,7 @@ import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'calloc.dart';
import 'dylib_utils.dart';
typedef NativeUnaryOp = Int32 Function(Int32);
@ -184,7 +185,7 @@ main() {
// pass an array / pointer as argument
Int64PointerUnOp assign1337Index1 = ffiTestFunctions
.lookupFunction<Int64PointerUnOp, Int64PointerUnOp>("Assign1337Index1");
Pointer<Int64> p2 = allocate(count: 2);
Pointer<Int64> p2 = calloc(2);
p2.value = 42;
p2[1] = 1000;
print(p2.elementAt(1).address.toRadixString(16));
@ -209,11 +210,11 @@ main() {
print(result);
print(result.runtimeType);
Pointer<Int64> p2 = allocate(count: 2);
Pointer<Int64> p2 = calloc(2);
result = nullableInt64ElemAt1(p2);
print(result);
print(result.runtimeType);
free(p2);
calloc.free(p2);
}
print("end main");

View file

@ -4,6 +4,9 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'calloc.dart';
import 'coordinate.dart';
import 'dylib_utils.dart';
@ -43,7 +46,7 @@ main() {
Pointer<NativeFunction<CoordinateTrice>> p2 =
ffiTestFunctions.lookup("CoordinateUnOpTrice");
CoordinateTrice coordinateUnOpTrice = p2.asFunction();
Coordinate c1 = Coordinate.allocate(10.0, 20.0, nullptr);
Coordinate c1 = Coordinate.allocate(calloc, 10.0, 20.0, nullptr);
c1.next = c1.addressOf;
Coordinate result =
coordinateUnOpTrice(transposeCoordinatePointer, c1.addressOf).ref;

View file

@ -4,11 +4,12 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'calloc.dart';
import 'coordinate.dart';
import 'dylib_utils.dart';
import 'package:ffi/ffi.dart';
typedef NativeCoordinateOp = Pointer<Coordinate> Function(Pointer<Coordinate>);
main() {
@ -23,8 +24,8 @@ main() {
ffiTestFunctions.lookup("TransposeCoordinate");
NativeCoordinateOp f1 = p1.asFunction();
Coordinate c1 = Coordinate.allocate(10.0, 20.0, nullptr);
Coordinate c2 = Coordinate.allocate(42.0, 84.0, c1.addressOf);
Coordinate c1 = Coordinate.allocate(calloc, 10.0, 20.0, nullptr);
Coordinate c2 = Coordinate.allocate(calloc, 42.0, 84.0, c1.addressOf);
c1.next = c2.addressOf;
Coordinate result = f1(c1.addressOf).ref;
@ -44,7 +45,7 @@ main() {
ffiTestFunctions.lookup("CoordinateElemAt1");
NativeCoordinateOp f1 = p1.asFunction();
Pointer<Coordinate> c1 = allocate<Coordinate>(count: 3);
Pointer<Coordinate> c1 = calloc<Coordinate>(3);
Pointer<Coordinate> c2 = c1.elementAt(1);
Pointer<Coordinate> c3 = c1.elementAt(2);
c1.ref.x = 10.0;

View file

@ -6,6 +6,7 @@ import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'calloc.dart';
import 'coordinate.dart';
main() {
@ -13,9 +14,9 @@ main() {
{
// Allocates each coordinate separately in c memory.
Coordinate c1 = Coordinate.allocate(10.0, 10.0, nullptr);
Coordinate c2 = Coordinate.allocate(20.0, 20.0, c1.addressOf);
Coordinate c3 = Coordinate.allocate(30.0, 30.0, c2.addressOf);
Coordinate c1 = Coordinate.allocate(calloc, 10.0, 10.0, nullptr);
Coordinate c2 = Coordinate.allocate(calloc, 20.0, 20.0, c1.addressOf);
Coordinate c3 = Coordinate.allocate(calloc, 30.0, 30.0, c2.addressOf);
c1.next = c3.addressOf;
Coordinate currentCoordinate = c1;
@ -24,14 +25,14 @@ main() {
print("${currentCoordinate.x}; ${currentCoordinate.y}");
}
free(c1.addressOf);
free(c2.addressOf);
free(c3.addressOf);
calloc.free(c1.addressOf);
calloc.free(c2.addressOf);
calloc.free(c3.addressOf);
}
{
// Allocates coordinates consecutively in c memory.
Pointer<Coordinate> c1 = allocate<Coordinate>(count: 3);
Pointer<Coordinate> c1 = calloc<Coordinate>(3);
Pointer<Coordinate> c2 = c1.elementAt(1);
Pointer<Coordinate> c3 = c1.elementAt(2);
c1.ref.x = 10.0;
@ -50,15 +51,15 @@ main() {
print("${currentCoordinate.x}; ${currentCoordinate.y}");
}
free(c1);
calloc.free(c1);
}
{
Coordinate c = Coordinate.allocate(10, 10, nullptr);
Coordinate c = Coordinate.allocate(calloc, 10, 10, nullptr);
print(c is Coordinate);
print(c is Pointer<Void>);
print(c is Pointer);
free(c.addressOf);
calloc.free(c.addressOf);
}
print("end main");

View file

@ -8,3 +8,5 @@
library sqlite;
export "src/database.dart";
export "src/ffi/calloc.dart" show calloc;

View file

@ -15,6 +15,8 @@ import "bindings/types.dart" hide Database;
import "bindings/constants.dart";
import "collections/closable_iterator.dart";
import 'ffi/calloc.dart';
/// [Database] represents an open connection to a SQLite database.
///
/// All functions against a database may throw [SQLiteError].
@ -27,13 +29,13 @@ class Database {
/// Open a database located at the file [path].
Database(String path,
[int flags = Flags.SQLITE_OPEN_READWRITE | Flags.SQLITE_OPEN_CREATE]) {
Pointer<Pointer<types.Database>> dbOut = allocate();
Pointer<Pointer<types.Database>> dbOut = calloc();
final pathC = Utf8.toUtf8(path);
final int resultCode =
bindings.sqlite3_open_v2(pathC, dbOut, flags, nullptr);
_database = dbOut.value;
free(dbOut);
free(pathC);
calloc.free(dbOut);
calloc.free(pathC);
if (resultCode == Errors.SQLITE_OK) {
_open = true;
@ -63,13 +65,13 @@ class Database {
/// Execute a query, discarding any returned rows.
void execute(String query) {
Pointer<Pointer<Statement>> statementOut = allocate();
Pointer<Pointer<Statement>> statementOut = calloc();
Pointer<Utf8> queryC = Utf8.toUtf8(query);
int resultCode = bindings.sqlite3_prepare_v2(
_database, queryC, -1, statementOut, nullptr);
Pointer<Statement> statement = statementOut.value;
free(statementOut);
free(queryC);
calloc.free(statementOut);
calloc.free(queryC);
while (resultCode == Errors.SQLITE_ROW || resultCode == Errors.SQLITE_OK) {
resultCode = bindings.sqlite3_step(statement);
@ -82,13 +84,13 @@ class Database {
/// Evaluate a query and return the resulting rows as an iterable.
Result query(String query) {
Pointer<Pointer<Statement>> statementOut = allocate();
Pointer<Pointer<Statement>> statementOut = calloc();
Pointer<Utf8> queryC = Utf8.toUtf8(query);
int resultCode = bindings.sqlite3_prepare_v2(
_database, queryC, -1, statementOut, nullptr);
Pointer<Statement> statement = statementOut.value;
free(statementOut);
free(queryC);
calloc.free(statementOut);
calloc.free(queryC);
if (resultCode != Errors.SQLITE_OK) {
bindings.sqlite3_finalize(statement);

View file

@ -7,6 +7,8 @@ import "dart:ffi";
import 'package:ffi/ffi.dart';
import 'calloc.dart';
/// [Arena] manages allocated C memory.
///
/// Arenas are zoned.
@ -24,7 +26,7 @@ class Arena {
/// Frees all memory pointed to by [Pointer]s in this arena.
void finalize() {
for (final ptr in _allocations) {
free(ptr);
calloc.free(ptr);
}
}

View file

@ -0,0 +1,109 @@
// Copyright (c) 2021, 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.
// TODO(https://dartbug.com/44621): Remove this copy when package:ffi can be
// rolled. We need to wait until the `Allocator` interface has rolled into
// Flutter.
import 'dart:ffi';
import 'dart:io';
final DynamicLibrary stdlib = Platform.isWindows
? DynamicLibrary.open('kernel32.dll')
: DynamicLibrary.process();
typedef PosixCallocNative = Pointer Function(IntPtr num, IntPtr size);
typedef PosixCalloc = Pointer Function(int num, int size);
final PosixCalloc posixCalloc =
stdlib.lookupFunction<PosixCallocNative, PosixCalloc>('calloc');
typedef PosixFreeNative = Void Function(Pointer);
typedef PosixFree = void Function(Pointer);
final PosixFree posixFree =
stdlib.lookupFunction<PosixFreeNative, PosixFree>('free');
typedef WinGetProcessHeapFn = Pointer Function();
final WinGetProcessHeapFn winGetProcessHeap = stdlib
.lookupFunction<WinGetProcessHeapFn, WinGetProcessHeapFn>('GetProcessHeap');
final Pointer processHeap = winGetProcessHeap();
typedef WinHeapAllocNative = Pointer Function(Pointer, Uint32, IntPtr);
typedef WinHeapAlloc = Pointer Function(Pointer, int, int);
final WinHeapAlloc winHeapAlloc =
stdlib.lookupFunction<WinHeapAllocNative, WinHeapAlloc>('HeapAlloc');
typedef WinHeapFreeNative = Int32 Function(
Pointer heap, Uint32 flags, Pointer memory);
typedef WinHeapFree = int Function(Pointer heap, int flags, Pointer memory);
final WinHeapFree winHeapFree =
stdlib.lookupFunction<WinHeapFreeNative, WinHeapFree>('HeapFree');
const int HEAP_ZERO_MEMORY = 8;
/// Manages memory on the native heap.
///
/// Initializes newly allocated memory to zero.
///
/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses
/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default
/// public heap.
class _CallocAllocator implements Allocator {
const _CallocAllocator();
/// Allocates [byteCount] bytes of zero-initialized of memory on the native
/// heap.
///
/// For POSIX-based systems, this uses `calloc`. On Windows, it uses
/// `HeapAlloc` against the default public heap.
///
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
/// satisfied.
// TODO: Stop ignoring alignment if it's large, for example for SSE data.
@override
Pointer<T> allocate<T extends NativeType>(int byteCount, {int? alignment}) {
Pointer<T> result;
if (Platform.isWindows) {
result = winHeapAlloc(processHeap, /*flags=*/ HEAP_ZERO_MEMORY, byteCount)
.cast();
} else {
result = posixCalloc(byteCount, 1).cast();
}
if (result.address == 0) {
throw ArgumentError('Could not allocate $byteCount bytes.');
}
return result;
}
/// Releases memory allocated 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] if the memory pointed to by [pointer] cannot be
/// freed.
///
// TODO(dartbug.com/36855): Once we have a ffi.Bool type we can use it instead
// of testing the return integer to be non-zero.
@override
void free(Pointer pointer) {
if (Platform.isWindows) {
if (winHeapFree(processHeap, /*flags=*/ 0, pointer) == 0) {
throw ArgumentError('Could not free $pointer.');
}
} else {
posixFree(pointer);
}
}
}
/// Manages memory on the native heap.
///
/// Initializes newly allocated memory to zero. Use [malloc] for unintialized
/// memory allocation.
///
/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses
/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default
/// public heap.
const Allocator calloc = _CallocAllocator();

View file

@ -168,6 +168,6 @@ void main() {
final String test = 'Hasta Mañana';
final medium = Utf8.toUtf8(test);
expect(test, medium.ref.toString());
free(medium);
calloc.free(medium);
});
}

111
samples_2/ffi/calloc.dart Normal file
View file

@ -0,0 +1,111 @@
// Copyright (c) 2021, 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.
// TODO(https://dartbug.com/44621): Remove this copy when package:ffi can be
// rolled. We need to wait until the `Allocator` interface has rolled into
// Flutter.
// @dart=2.9
import 'dart:ffi';
import 'dart:io';
final DynamicLibrary stdlib = Platform.isWindows
? DynamicLibrary.open('kernel32.dll')
: DynamicLibrary.process();
typedef PosixCallocNative = Pointer Function(IntPtr num, IntPtr size);
typedef PosixCalloc = Pointer Function(int num, int size);
final PosixCalloc posixCalloc =
stdlib.lookupFunction<PosixCallocNative, PosixCalloc>('calloc');
typedef PosixFreeNative = Void Function(Pointer);
typedef PosixFree = void Function(Pointer);
final PosixFree posixFree =
stdlib.lookupFunction<PosixFreeNative, PosixFree>('free');
typedef WinGetProcessHeapFn = Pointer Function();
final WinGetProcessHeapFn winGetProcessHeap = stdlib
.lookupFunction<WinGetProcessHeapFn, WinGetProcessHeapFn>('GetProcessHeap');
final Pointer processHeap = winGetProcessHeap();
typedef WinHeapAllocNative = Pointer Function(Pointer, Uint32, IntPtr);
typedef WinHeapAlloc = Pointer Function(Pointer, int, int);
final WinHeapAlloc winHeapAlloc =
stdlib.lookupFunction<WinHeapAllocNative, WinHeapAlloc>('HeapAlloc');
typedef WinHeapFreeNative = Int32 Function(
Pointer heap, Uint32 flags, Pointer memory);
typedef WinHeapFree = int Function(Pointer heap, int flags, Pointer memory);
final WinHeapFree winHeapFree =
stdlib.lookupFunction<WinHeapFreeNative, WinHeapFree>('HeapFree');
const int HEAP_ZERO_MEMORY = 8;
/// Manages memory on the native heap.
///
/// Initializes newly allocated memory to zero.
///
/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses
/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default
/// public heap.
class _CallocAllocator implements Allocator {
const _CallocAllocator();
/// Allocates [byteCount] bytes of zero-initialized of memory on the native
/// heap.
///
/// For POSIX-based systems, this uses `calloc`. On Windows, it uses
/// `HeapAlloc` against the default public heap.
///
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
/// satisfied.
// TODO: Stop ignoring alignment if it's large, for example for SSE data.
@override
Pointer<T> allocate<T extends NativeType>(int byteCount, {int alignment}) {
Pointer<T> result;
if (Platform.isWindows) {
result = winHeapAlloc(processHeap, /*flags=*/ HEAP_ZERO_MEMORY, byteCount)
.cast();
} else {
result = posixCalloc(byteCount, 1).cast();
}
if (result.address == 0) {
throw ArgumentError('Could not allocate $byteCount bytes.');
}
return result;
}
/// Releases memory allocated 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] if the memory pointed to by [pointer] cannot be
/// freed.
///
// TODO(dartbug.com/36855): Once we have a ffi.Bool type we can use it instead
// of testing the return integer to be non-zero.
@override
void free(Pointer pointer) {
if (Platform.isWindows) {
if (winHeapFree(processHeap, /*flags=*/ 0, pointer) == 0) {
throw ArgumentError('Could not free $pointer.');
}
} else {
posixFree(pointer);
}
}
}
/// Manages memory on the native heap.
///
/// Initializes newly allocated memory to zero. Use [malloc] for unintialized
/// memory allocation.
///
/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses
/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default
/// public heap.
const Allocator calloc = _CallocAllocator();

View file

@ -18,8 +18,9 @@ class Coordinate extends Struct {
Pointer<Coordinate> next;
factory Coordinate.allocate(double x, double y, Pointer<Coordinate> next) {
return allocate<Coordinate>().ref
factory Coordinate.allocate(
Allocator allocator, double x, double y, Pointer<Coordinate> next) {
return allocator<Coordinate>().ref
..x = x
..y = y
..next = next;

View file

@ -7,48 +7,36 @@
// @dart = 2.9
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;
import 'package:ffi/ffi.dart';
/// Manages native resources.
import '../calloc.dart';
/// Keeps track of all allocated memory and frees all allocated memory on
/// [releaseAll].
///
/// 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});
}
/// Wraps an [Allocator] to do the actual allocation and freeing.
class Pool implements Allocator {
/// The [Allocator] used for allocation and freeing.
final Allocator _wrappedAllocator;
Pool(this._wrappedAllocator);
/// 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.
/// Allocates memory on the native heap by using the allocator supplied to
/// the constructor.
///
/// 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);
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
/// satisfied.
@override
Pointer<T> allocate<T extends NativeType>(int numBytes, {int alignment}) {
final p = _wrappedAllocator.allocate<T>(numBytes, alignment: alignment);
_managedMemoryPointers.add(p);
return p;
}
@ -73,17 +61,21 @@ class Pool implements ResourceManager {
}
_managedResourceReleaseCallbacks.clear();
for (final p in _managedMemoryPointers) {
Unmanaged().free(p);
_wrappedAllocator.free(p);
}
_managedMemoryPointers.clear();
}
@override
void free(Pointer<NativeType> pointer) => throw UnsupportedError(
"Individually freeing Pool allocated memory is not allowed");
}
/// 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();
R using<R>(R Function(Pool) f, [Allocator wrappedAllocator = calloc]) {
final p = Pool(wrappedAllocator);
try {
return f(p);
} finally {
@ -98,8 +90,8 @@ R using<R>(R Function(Pool) f) {
/// 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();
R usePool<R>(R Function() f, [Allocator wrappedAllocator = calloc]) {
final p = Pool(wrappedAllocator);
try {
return runZoned(() => f(),
zoneValues: {#_pool: p},
@ -119,78 +111,3 @@ class RethrownError {
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;

View file

@ -11,6 +11,7 @@ import 'dart:ffi';
import 'package:expect/expect.dart';
import 'pool.dart';
import 'utf8_helpers.dart';
import '../dylib_utils.dart';
main() {
@ -23,7 +24,7 @@ main() {
// To ensure resources are freed, wrap them in a [using] call.
using((Pool pool) {
final p = pool.allocate<Int64>(count: 2);
final p = pool<Int64>(2);
p[0] = 24;
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
print(p[1]);
@ -33,14 +34,14 @@ main() {
// Resources are freed also when abnormal control flow occurs.
try {
using((Pool pool) {
final p = pool.allocate<Int64>(count: 2);
final p = pool<Int64>(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.
// `calloc.free(p)` has been called.
} on Exception catch (e) {
print("Caught exception: $e");
}
@ -48,8 +49,8 @@ main() {
// 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);
final p = pool<Int64>(2);
final p2 = pool<Int64>(2);
p[0] = 1;
p[1] = 2;
MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
@ -60,7 +61,7 @@ main() {
// 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);
return pool<Int64>(2);
}
using((Pool pool) {

View file

@ -11,6 +11,7 @@ import 'dart:ffi';
import 'package:expect/expect.dart';
import 'pool.dart';
import 'utf8_helpers.dart';
import '../dylib_utils.dart';
main() {
@ -23,7 +24,7 @@ main() {
// To ensure resources are freed, wrap them in a [using] call.
usePool(() {
final p = currentPool.allocate<Int64>(count: 2);
final p = currentPool<Int64>(2);
p[0] = 24;
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), sizeOf<Int64>());
print(p[1]);
@ -33,7 +34,7 @@ main() {
// Resources are freed also when abnormal control flow occurs.
try {
usePool(() {
final p = currentPool.allocate<Int64>(count: 2);
final p = currentPool<Int64>(2);
p[0] = 25;
MemMove(p.elementAt(1).cast<Void>(), p.cast<Void>(), 8);
print(p[1]);
@ -48,8 +49,8 @@ main() {
// 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);
final p = currentPool<Int64>(2);
final p2 = currentPool<Int64>(2);
p[0] = 1;
p[1] = 2;
MemMove(p2.cast<Void>(), p.cast<Void>(), 2 * sizeOf<Int64>());
@ -60,7 +61,7 @@ main() {
// 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);
return currentPool<Int64>(2);
}
usePool(() {

View file

@ -9,8 +9,10 @@
import 'dart:ffi';
import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';
import 'pool.dart';
import 'utf8_helpers.dart';
import '../calloc.dart';
import '../dylib_utils.dart';
main() {
@ -24,15 +26,15 @@ main() {
// To ensure resources are freed, call free manually.
//
// For automatic management use a Pool.
final p = unmanaged.allocate<Int64>(count: 2);
final p = calloc<Int64>(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);
calloc.free(p);
// Using Strings.
final p2 = "Hello world!".toUtf8(unmanaged);
final p2 = "Hello world!".toUtf8(calloc);
print(p2.contents());
unmanaged.free(p2);
calloc.free(p2);
}

View file

@ -0,0 +1,57 @@
// Copyright (c) 2021, 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.
// @dart = 2.9
import 'dart:convert';
import 'dart:ffi';
import 'dart:typed_data';
import 'package:ffi/ffi.dart';
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. See [Utf8Encoder] for details on encoding.
///
/// Returns a [allocator]-allocated pointer to the result.
Pointer<Utf8> toUtf8(Allocator allocator) {
final units = utf8.encode(this);
final Pointer<Uint8> result = allocator<Uint8>(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> {
/// The length of a null-terminated string the number of (one-byte)
/// characters before the first null byte.
int get 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.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;

View file

@ -9,6 +9,8 @@ import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:expect/expect.dart';
import 'calloc.dart';
/// typedef struct {
/// unsigned int bold : 1;
/// unsigned int underline : 2;
@ -95,7 +97,7 @@ extension IntBitField on int {
}
main() {
final p = allocate<ScreenCellAttrs>(count: 3);
final p = calloc<ScreenCellAttrs>(3);
// Zeroes out all fields.
p.ref.bits = 0;
@ -121,5 +123,5 @@ main() {
// A check for automated testing.
Expect.equals(1933, p.ref.bits);
free(p);
calloc.free(p);
}

View file

@ -7,29 +7,31 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'calloc.dart';
main() {
print('start main');
{
// Basic operation: allocate, get, set, and free.
Pointer<Int64> p = allocate();
Pointer<Int64> p = calloc();
p.value = 42;
int pValue = p.value;
print('${p.runtimeType} value: ${pValue}');
free(p);
calloc.free(p);
}
{
// Undefined behavior before set.
Pointer<Int64> p = allocate();
Pointer<Int64> p = calloc();
int pValue = p.value;
print('If not set, returns garbage: ${pValue}');
free(p);
calloc.free(p);
}
{
// Pointers can be created from an address.
Pointer<Int64> pHelper = allocate();
Pointer<Int64> pHelper = calloc();
pHelper.value = 1337;
int address = pHelper.address;
@ -38,13 +40,13 @@ main() {
Pointer<Int64> p = Pointer.fromAddress(address);
print('${p.runtimeType} value: ${p.value}');
free(pHelper);
calloc.free(pHelper);
}
{
// Address is zeroed out after free.
Pointer<Int64> p = allocate();
free(p);
Pointer<Int64> p = calloc();
calloc.free(p);
print('After free, address is zero: ${p.address}');
}
@ -52,13 +54,13 @@ main() {
// Allocating too much throws an exception.
try {
int maxMint = 9223372036854775807; // 2^63 - 1
allocate<Int64>(count: maxMint);
calloc<Int64>(maxMint);
} on Error {
print('Expected exception on allocating too much');
}
try {
int maxInt1_8 = 1152921504606846975; // 2^60 -1
allocate<Int64>(count: maxInt1_8);
calloc<Int64>(maxInt1_8);
} on Error {
print('Expected exception on allocating too much');
}
@ -67,7 +69,7 @@ main() {
{
// Pointers can be cast into another type,
// resulting in the corresponding bits read.
Pointer<Int64> p1 = allocate();
Pointer<Int64> p1 = calloc();
p1.value = 9223372036854775807; // 2^63 - 1
Pointer<Int32> p2 = p1.cast();
@ -76,61 +78,61 @@ main() {
Pointer<Int32> p3 = p2.elementAt(1);
print('${p3.runtimeType} value: ${p3.value}'); // 2^31 - 1
free(p1);
calloc.free(p1);
}
{
// Data can be tightly packed in memory.
Pointer<Int8> p = allocate(count: 8);
Pointer<Int8> p = calloc(8);
for (var i in [0, 1, 2, 3, 4, 5, 6, 7]) {
p.elementAt(i).value = i * 3;
}
for (var i in [0, 1, 2, 3, 4, 5, 6, 7]) {
print('p.elementAt($i) value: ${p.elementAt(i).value}');
}
free(p);
calloc.free(p);
}
{
// Values that don't fit are truncated.
Pointer<Int32> p11 = allocate();
Pointer<Int32> p11 = calloc();
p11.value = 9223372036854775807;
print(p11);
free(p11);
calloc.free(p11);
}
{
// Doubles.
Pointer<Double> p = allocate();
Pointer<Double> p = calloc();
p.value = 3.14159265359;
print('${p.runtimeType} value: ${p.value}');
p.value = 3.14;
print('${p.runtimeType} value: ${p.value}');
free(p);
calloc.free(p);
}
{
// Floats.
Pointer<Float> p = allocate();
Pointer<Float> p = calloc();
p.value = 3.14159265359;
print('${p.runtimeType} value: ${p.value}');
p.value = 3.14;
print('${p.runtimeType} value: ${p.value}');
free(p);
calloc.free(p);
}
{
// IntPtr varies in size based on whether the platform is 32 or 64 bit.
// Addresses of pointers fit in this size.
Pointer<IntPtr> p = allocate();
Pointer<IntPtr> p = calloc();
int p14addr = p.address;
p.value = p14addr;
int pValue = p.value;
print('${p.runtimeType} value: ${pValue}');
free(p);
calloc.free(p);
}
{
@ -138,19 +140,19 @@ main() {
// The size of the element it is pointing to is undefined,
// they cannot be allocated, read, or written.
Pointer<IntPtr> p1 = allocate();
Pointer<IntPtr> p1 = calloc();
Pointer<Void> p2 = p1.cast();
print('${p2.runtimeType} address: ${p2.address}');
free(p1);
calloc.free(p1);
}
{
// Pointer to a pointer to something.
Pointer<Int16> pHelper = allocate();
Pointer<Int16> pHelper = calloc();
pHelper.value = 17;
Pointer<Pointer<Int16>> p = allocate();
Pointer<Pointer<Int16>> p = calloc();
// Storing into a pointer pointer automatically unboxes.
p.value = pHelper;
@ -162,31 +164,31 @@ main() {
int pValue = p.value.value;
print('${p.runtimeType} value\'s value: ${pValue}');
free(p);
free(pHelper);
calloc.free(p);
calloc.free(pHelper);
}
{
// The pointer to pointer types must match up.
Pointer<Int8> pHelper = allocate();
Pointer<Int8> pHelper = calloc();
pHelper.value = 123;
Pointer<Pointer<Int16>> p = allocate();
Pointer<Pointer<Int16>> p = calloc();
// Trying to store `pHelper` into `p.val` would result in a type mismatch.
free(pHelper);
free(p);
calloc.free(pHelper);
calloc.free(p);
}
{
// `nullptr` points to address 0 in c++.
Pointer<Pointer<Int8>> pointerToPointer = allocate();
Pointer<Pointer<Int8>> pointerToPointer = calloc();
Pointer<Int8> value = nullptr;
pointerToPointer.value = value;
value = pointerToPointer.value;
print("Loading a pointer to the 0 address is null: ${value}");
free(pointerToPointer);
calloc.free(pointerToPointer);
}
{
@ -207,7 +209,7 @@ main() {
head.value = value;
return;
}
Pointer<IntPtr> next = allocate<IntPtr>();
Pointer<IntPtr> next = calloc<IntPtr>();
head.value = next.address;
createChain(next, length - 1, value);
}
@ -222,7 +224,7 @@ main() {
void freeChain(Pointer<IntPtr> head, int length) {
Pointer<IntPtr> next = Pointer.fromAddress(head.value);
free(head);
calloc.free(head);
if (length == 0) {
return;
}
@ -230,7 +232,7 @@ main() {
}
int length = 10;
Pointer<IntPtr> head = allocate();
Pointer<IntPtr> head = calloc();
createChain(head, length, 512);
int tailValue = getChainValue(head, length);
print('tailValue: ${tailValue}');

View file

@ -8,6 +8,7 @@ import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'calloc.dart';
import 'dylib_utils.dart';
typedef NativeUnaryOp = Int32 Function(Int32);
@ -186,7 +187,7 @@ main() {
// pass an array / pointer as argument
Int64PointerUnOp assign1337Index1 = ffiTestFunctions
.lookupFunction<Int64PointerUnOp, Int64PointerUnOp>("Assign1337Index1");
Pointer<Int64> p2 = allocate(count: 2);
Pointer<Int64> p2 = calloc(2);
p2.value = 42;
p2[1] = 1000;
print(p2.elementAt(1).address.toRadixString(16));
@ -251,11 +252,11 @@ main() {
print(result);
print(result.runtimeType);
Pointer<Int64> p2 = allocate(count: 2);
Pointer<Int64> p2 = calloc(2);
result = nullableInt64ElemAt1(p2);
print(result);
print(result.runtimeType);
free(p2);
calloc.free(p2);
}
print("end main");

View file

@ -6,6 +6,9 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'calloc.dart';
import 'coordinate.dart';
import 'dylib_utils.dart';
@ -45,7 +48,7 @@ main() {
Pointer<NativeFunction<CoordinateTrice>> p2 =
ffiTestFunctions.lookup("CoordinateUnOpTrice");
CoordinateTrice coordinateUnOpTrice = p2.asFunction();
Coordinate c1 = Coordinate.allocate(10.0, 20.0, nullptr);
Coordinate c1 = Coordinate.allocate(calloc, 10.0, 20.0, nullptr);
c1.next = c1.addressOf;
Coordinate result =
coordinateUnOpTrice(transposeCoordinatePointer, c1.addressOf).ref;

View file

@ -6,11 +6,12 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'calloc.dart';
import 'coordinate.dart';
import 'dylib_utils.dart';
import 'package:ffi/ffi.dart';
typedef NativeCoordinateOp = Pointer<Coordinate> Function(Pointer<Coordinate>);
main() {
@ -25,8 +26,8 @@ main() {
ffiTestFunctions.lookup("TransposeCoordinate");
NativeCoordinateOp f1 = p1.asFunction();
Coordinate c1 = Coordinate.allocate(10.0, 20.0, nullptr);
Coordinate c2 = Coordinate.allocate(42.0, 84.0, c1.addressOf);
Coordinate c1 = Coordinate.allocate(calloc, 10.0, 20.0, nullptr);
Coordinate c2 = Coordinate.allocate(calloc, 42.0, 84.0, c1.addressOf);
c1.next = c2.addressOf;
Coordinate result = f1(c1.addressOf).ref;
@ -46,7 +47,7 @@ main() {
ffiTestFunctions.lookup("CoordinateElemAt1");
NativeCoordinateOp f1 = p1.asFunction();
Pointer<Coordinate> c1 = allocate<Coordinate>(count: 3);
Pointer<Coordinate> c1 = calloc<Coordinate>(3);
Pointer<Coordinate> c2 = c1.elementAt(1);
Pointer<Coordinate> c3 = c1.elementAt(2);
c1.ref.x = 10.0;

View file

@ -8,6 +8,7 @@ import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'calloc.dart';
import 'coordinate.dart';
main() {
@ -15,9 +16,9 @@ main() {
{
// Allocates each coordinate separately in c memory.
Coordinate c1 = Coordinate.allocate(10.0, 10.0, nullptr);
Coordinate c2 = Coordinate.allocate(20.0, 20.0, c1.addressOf);
Coordinate c3 = Coordinate.allocate(30.0, 30.0, c2.addressOf);
Coordinate c1 = Coordinate.allocate(calloc, 10.0, 10.0, nullptr);
Coordinate c2 = Coordinate.allocate(calloc, 20.0, 20.0, c1.addressOf);
Coordinate c3 = Coordinate.allocate(calloc, 30.0, 30.0, c2.addressOf);
c1.next = c3.addressOf;
Coordinate currentCoordinate = c1;
@ -26,14 +27,14 @@ main() {
print("${currentCoordinate.x}; ${currentCoordinate.y}");
}
free(c1.addressOf);
free(c2.addressOf);
free(c3.addressOf);
calloc.free(c1.addressOf);
calloc.free(c2.addressOf);
calloc.free(c3.addressOf);
}
{
// Allocates coordinates consecutively in c memory.
Pointer<Coordinate> c1 = allocate<Coordinate>(count: 3);
Pointer<Coordinate> c1 = calloc<Coordinate>(3);
Pointer<Coordinate> c2 = c1.elementAt(1);
Pointer<Coordinate> c3 = c1.elementAt(2);
c1.ref.x = 10.0;
@ -52,15 +53,15 @@ main() {
print("${currentCoordinate.x}; ${currentCoordinate.y}");
}
free(c1);
calloc.free(c1);
}
{
Coordinate c = Coordinate.allocate(10, 10, nullptr);
Coordinate c = Coordinate.allocate(calloc, 10, 10, nullptr);
print(c is Coordinate);
print(c is Pointer<Void>);
print(c is Pointer);
free(c.addressOf);
calloc.free(c.addressOf);
}
print("end main");

View file

@ -10,3 +10,5 @@
library sqlite;
export "src/database.dart";
export "src/ffi/calloc.dart" show calloc;

View file

@ -17,6 +17,8 @@ import "bindings/types.dart" hide Database;
import "bindings/constants.dart";
import "collections/closable_iterator.dart";
import 'ffi/calloc.dart';
/// [Database] represents an open connection to a SQLite database.
///
/// All functions against a database may throw [SQLiteError].
@ -29,13 +31,13 @@ class Database {
/// Open a database located at the file [path].
Database(String path,
[int flags = Flags.SQLITE_OPEN_READWRITE | Flags.SQLITE_OPEN_CREATE]) {
Pointer<Pointer<types.Database>> dbOut = allocate();
Pointer<Pointer<types.Database>> dbOut = calloc();
final pathC = Utf8.toUtf8(path);
final int resultCode =
bindings.sqlite3_open_v2(pathC, dbOut, flags, nullptr);
_database = dbOut.value;
free(dbOut);
free(pathC);
calloc.free(dbOut);
calloc.free(pathC);
if (resultCode == Errors.SQLITE_OK) {
_open = true;
@ -65,13 +67,13 @@ class Database {
/// Execute a query, discarding any returned rows.
void execute(String query) {
Pointer<Pointer<Statement>> statementOut = allocate();
Pointer<Pointer<Statement>> statementOut = calloc();
Pointer<Utf8> queryC = Utf8.toUtf8(query);
int resultCode = bindings.sqlite3_prepare_v2(
_database, queryC, -1, statementOut, nullptr);
Pointer<Statement> statement = statementOut.value;
free(statementOut);
free(queryC);
calloc.free(statementOut);
calloc.free(queryC);
while (resultCode == Errors.SQLITE_ROW || resultCode == Errors.SQLITE_OK) {
resultCode = bindings.sqlite3_step(statement);
@ -84,13 +86,13 @@ class Database {
/// Evaluate a query and return the resulting rows as an iterable.
Result query(String query) {
Pointer<Pointer<Statement>> statementOut = allocate();
Pointer<Pointer<Statement>> statementOut = calloc();
Pointer<Utf8> queryC = Utf8.toUtf8(query);
int resultCode = bindings.sqlite3_prepare_v2(
_database, queryC, -1, statementOut, nullptr);
Pointer<Statement> statement = statementOut.value;
free(statementOut);
free(queryC);
calloc.free(statementOut);
calloc.free(queryC);
if (resultCode != Errors.SQLITE_OK) {
bindings.sqlite3_finalize(statement);

View file

@ -9,6 +9,8 @@ import "dart:ffi";
import 'package:ffi/ffi.dart';
import 'calloc.dart';
/// [Arena] manages allocated C memory.
///
/// Arenas are zoned.
@ -26,7 +28,7 @@ class Arena {
/// Frees all memory pointed to by [Pointer]s in this arena.
void finalize() {
for (final ptr in _allocations) {
free(ptr);
calloc.free(ptr);
}
}

View file

@ -0,0 +1,111 @@
// Copyright (c) 2021, 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.
// TODO(https://dartbug.com/44621): Remove this copy when package:ffi can be
// rolled. We need to wait until the `Allocator` interface has rolled into
// Flutter.
// @dart=2.9
import 'dart:ffi';
import 'dart:io';
final DynamicLibrary stdlib = Platform.isWindows
? DynamicLibrary.open('kernel32.dll')
: DynamicLibrary.process();
typedef PosixCallocNative = Pointer Function(IntPtr num, IntPtr size);
typedef PosixCalloc = Pointer Function(int num, int size);
final PosixCalloc posixCalloc =
stdlib.lookupFunction<PosixCallocNative, PosixCalloc>('calloc');
typedef PosixFreeNative = Void Function(Pointer);
typedef PosixFree = void Function(Pointer);
final PosixFree posixFree =
stdlib.lookupFunction<PosixFreeNative, PosixFree>('free');
typedef WinGetProcessHeapFn = Pointer Function();
final WinGetProcessHeapFn winGetProcessHeap = stdlib
.lookupFunction<WinGetProcessHeapFn, WinGetProcessHeapFn>('GetProcessHeap');
final Pointer processHeap = winGetProcessHeap();
typedef WinHeapAllocNative = Pointer Function(Pointer, Uint32, IntPtr);
typedef WinHeapAlloc = Pointer Function(Pointer, int, int);
final WinHeapAlloc winHeapAlloc =
stdlib.lookupFunction<WinHeapAllocNative, WinHeapAlloc>('HeapAlloc');
typedef WinHeapFreeNative = Int32 Function(
Pointer heap, Uint32 flags, Pointer memory);
typedef WinHeapFree = int Function(Pointer heap, int flags, Pointer memory);
final WinHeapFree winHeapFree =
stdlib.lookupFunction<WinHeapFreeNative, WinHeapFree>('HeapFree');
const int HEAP_ZERO_MEMORY = 8;
/// Manages memory on the native heap.
///
/// Initializes newly allocated memory to zero.
///
/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses
/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default
/// public heap.
class _CallocAllocator implements Allocator {
const _CallocAllocator();
/// Allocates [byteCount] bytes of zero-initialized of memory on the native
/// heap.
///
/// For POSIX-based systems, this uses `calloc`. On Windows, it uses
/// `HeapAlloc` against the default public heap.
///
/// Throws an [ArgumentError] if the number of bytes or alignment cannot be
/// satisfied.
// TODO: Stop ignoring alignment if it's large, for example for SSE data.
@override
Pointer<T> allocate<T extends NativeType>(int byteCount, {int alignment}) {
Pointer<T> result;
if (Platform.isWindows) {
result = winHeapAlloc(processHeap, /*flags=*/ HEAP_ZERO_MEMORY, byteCount)
.cast();
} else {
result = posixCalloc(byteCount, 1).cast();
}
if (result.address == 0) {
throw ArgumentError('Could not allocate $byteCount bytes.');
}
return result;
}
/// Releases memory allocated 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] if the memory pointed to by [pointer] cannot be
/// freed.
///
// TODO(dartbug.com/36855): Once we have a ffi.Bool type we can use it instead
// of testing the return integer to be non-zero.
@override
void free(Pointer pointer) {
if (Platform.isWindows) {
if (winHeapFree(processHeap, /*flags=*/ 0, pointer) == 0) {
throw ArgumentError('Could not free $pointer.');
}
} else {
posixFree(pointer);
}
}
}
/// Manages memory on the native heap.
///
/// Initializes newly allocated memory to zero. Use [malloc] for unintialized
/// memory allocation.
///
/// For POSIX-based systems, this uses `calloc` and `free`. On Windows, it uses
/// `HeapAlloc` with [HEAP_ZERO_MEMORY] and `HeapFree` against the default
/// public heap.
const Allocator calloc = _CallocAllocator();

View file

@ -172,6 +172,6 @@ void main() {
final String test = 'Hasta Mañana';
final medium = Utf8.toUtf8(test);
expect(test, medium.ref.toString());
free(medium);
calloc.free(medium);
});
}