Skip to content
This repository has been archived by the owner on Jan 17, 2024. It is now read-only.

0.3.0-dev.0 Allocator and Opaque #72

Merged
merged 29 commits into from
Feb 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
75407c6
Add MallocAllocator and deprecate allocate and free
dcharkes Jan 6, 2021
96a619c
Migrate Utf8, Utf16, tests, and example to MallocAllocator
dcharkes Jan 6, 2021
3fb5dc0
Migrate Utf8 and Utf16 to Opaque and remove Utf8.toString
dcharkes Jan 6, 2021
9731e95
Add alignment named argument to allocate
dcharkes Jan 7, 2021
753e01e
Merge commit '9731e9560a0a500f78bdf6fd34d1354cc7413898' into api/opaque
dcharkes Jan 7, 2021
1daa8bd
Remove allocate and free
dcharkes Jan 8, 2021
410c0e4
Merge branch 'api/remove-allocate' into api/opaque
dcharkes Jan 8, 2021
01a67e9
Add CallocAllocator
dcharkes Jan 9, 2021
6c06193
Merge branch 'api/allocator' into api/remove-allocate
dcharkes Jan 11, 2021
3793e13
Merge branch 'api/remove-allocate' into api/opaque
dcharkes Jan 11, 2021
ec14a80
export `calloc`
dcharkes Jan 11, 2021
84f98cc
Merge branch 'api/allocator' into api/remove-allocate
dcharkes Jan 11, 2021
dfb03d0
Merge branch 'api/remove-allocate' into api/opaque
dcharkes Jan 11, 2021
5347744
Make `MallocAllocator` and `CallocAllocator` types private
dcharkes Jan 11, 2021
702f65d
naming and documentation changes
dcharkes Jan 11, 2021
45c859b
Merge branch 'api/allocator' into api/remove-allocate
dcharkes Jan 11, 2021
4998ffa
Merge branch 'api/remove-allocate' into api/opaque
dcharkes Jan 11, 2021
ff52df7
Update allocate invocations to positional count argument
dcharkes Jan 11, 2021
3a1a65c
Merge branch 'api/allocator' into api/remove-allocate
dcharkes Jan 11, 2021
26bd247
Merge branch 'api/remove-allocate' into api/opaque
dcharkes Jan 11, 2021
fd32695
Update `allocate` top level to `call` extension method
dcharkes Jan 11, 2021
9dbffd2
Merge branch 'api/allocator' into api/remove-allocate
dcharkes Jan 11, 2021
d0a74c3
Merge branch 'api/remove-allocate' into api/opaque
dcharkes Jan 11, 2021
fd00fb2
Revert "Remove allocate and free"
dcharkes Jan 20, 2021
ad6700d
Fix typo and make `calloc` default
dcharkes Jan 20, 2021
6b07a3d
Merge commit 'ad6700de10ca3af16f0c3d9ff8aa15d2bd7cd21c' into api/remo…
dcharkes Jan 21, 2021
2e8e224
Revert "Revert "Remove allocate and free""
dcharkes Jan 21, 2021
1ab9116
0.3.0-dev.0
dcharkes Feb 1, 2021
e05fc9c
address comments
dcharkes Feb 1, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 0.3.0-nullsafety.0

Changes `Utf8` and `Utf16` to extend `Opaque` instead of `Struct`.
This means `.ref` is no longer available and `Pointer<Utf(..)>` should be used.
See [breaking change #44622](https://github.com/dart-lang/sdk/issues/44622) for more info.

Removes `allocate` and `free`.
Instead, introduces `calloc` which implements the new `Allocator` interface.
See [breaking change #44621](https://github.com/dart-lang/sdk/issues/44621) for more info.

This pre-release requires Dart `2.12.0-265.0.dev` or greater.

## 0.2.0-nullsafety.1

Adds an optional named `length` argument to `Utf8.fromUtf8()`.
Expand Down
8 changes: 4 additions & 4 deletions example/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import 'dart:ffi';
import 'package:ffi/ffi.dart';

void main() {
// Allocate and free some native memory with malloc and free.
final pointer = allocate<Uint8>();
// Allocate and free some native memory with calloc and free.
final pointer = calloc<Uint8>();
pointer.value = 3;
print(pointer.value);
free(pointer);
calloc.free(pointer);

// Use the Utf8 helper to encode zero-terminated UTF-8 strings in native memory.
final String myString = '😎👿💬';
final Pointer<Utf8> charPointer = Utf8.toUtf8(myString);
print('First byte is: ${charPointer.cast<Uint8>().value}');
print(Utf8.fromUtf8(charPointer));
free(charPointer);
calloc.free(charPointer);
}
2 changes: 1 addition & 1 deletion lib/ffi.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

export 'src/utf8.dart';
export 'src/utf16.dart';
export 'src/allocation.dart' show allocate, free;
export 'src/allocation.dart' show calloc, malloc;
153 changes: 125 additions & 28 deletions lib/src/allocation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ typedef PosixMalloc = Pointer Function(int);
final PosixMalloc posixMalloc =
stdlib.lookupFunction<PosixMallocNative, PosixMalloc>('malloc');

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 =
Expand All @@ -36,43 +41,135 @@ typedef WinHeapFree = int Function(Pointer heap, int flags, Pointer memory);
final WinHeapFree winHeapFree =
stdlib.lookupFunction<WinHeapFreeNative, WinHeapFree>('HeapFree');

/// Allocates memory on the native heap.
const int HEAP_ZERO_MEMORY = 8;

/// Manages 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.
/// Does not initialize newly allocated memory to zero. Use [_CallocAllocator]
/// for zero-initialized memory on allocation.
///
/// Throws an ArgumentError on failure to allocate.
Pointer<T> allocate<T extends NativeType>({int count = 1}) {
final int totalSize = count * sizeOf<T>();
Pointer<T> result;
if (Platform.isWindows) {
result = winHeapAlloc(processHeap, /*flags=*/ 0, totalSize).cast();
} else {
result = posixMalloc(totalSize).cast();
/// For POSIX-based systems, this uses `malloc` and `free`. On Windows, it uses
/// `HeapAlloc` and `HeapFree` against the default public heap.
class _MallocAllocator implements Allocator {
const _MallocAllocator();

/// Allocates [byteCount] bytes of of unitialized memory on the native heap.
///
/// For POSIX-based systems, this uses `malloc`. 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=*/ 0, byteCount).cast();
} else {
result = posixMalloc(byteCount).cast();
}
if (result.address == 0) {
throw ArgumentError('Could not allocate $byteCount bytes.');
}
return result;
}
if (result.address == 0) {
throw ArgumentError('Could not allocate $totalSize bytes.');

/// 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);
}
}
return result;
}

/// Releases memory on the native heap.
/// Manages 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].
/// Does not initialize newly allocated memory to zero. Use [calloc] for
/// zero-initialized memory allocation.
///
/// For POSIX-based systems, this uses `malloc` and `free`. On Windows, it uses
/// `HeapAlloc` and `HeapFree` against the default public heap.
const Allocator malloc = _MallocAllocator();

/// Manages memory on the native heap.
///
/// Throws an ArgumentError on failure to free.
/// Initializes newly allocated memory to zero.
///
// 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.
void free(Pointer pointer) {
if (Platform.isWindows) {
if (winHeapFree(processHeap, /*flags=*/ 0, pointer) == 0) {
throw ArgumentError('Could not free $pointer.');
/// 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 `malloc`. 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);
}
} else {
posixFree(pointer);
}
}

/// Manages memory on the native heap.
///
/// Initializes newly allocated memory to zero. Use [malloc] for uninitialized
/// 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();
8 changes: 4 additions & 4 deletions lib/src/utf16.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ import 'package:ffi/ffi.dart';
///
/// [Utf16] is represented as a struct so that `Pointer<Utf16>` can be used in
/// native function signatures.
class Utf16 extends Struct {
class Utf16 extends Opaque {
/// Convert a [String] to a UTF-16 encoded zero-terminated C string.
///
/// If [string] contains NULL characters, the converted string will be truncated
/// prematurely. Unpaired surrogate code points in [string] will be preserved
/// in the UTF-16 encoded result. See [Utf16Encoder] for details on encoding.
///
/// Returns a malloc-allocated pointer to the result.
static Pointer<Utf16> toUtf16(String string) {
/// Returns a [allocator]-allocated pointer to the result.
static Pointer<Utf16> toUtf16(String string, {Allocator allocator = calloc}) {
final units = string.codeUnits;
final Pointer<Uint16> result = allocate<Uint16>(count: units.length + 1);
final Pointer<Uint16> result = allocator<Uint16>(units.length + 1);
final Uint16List nativeString = result.asTypedList(units.length + 1);
nativeString.setAll(0, units);
nativeString[units.length] = 0;
Expand Down
11 changes: 4 additions & 7 deletions lib/src/utf8.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ final int _maxSize = sizeOf<IntPtr>() == 8 ? _kMaxSmi64 : _kMaxSmi32;
//
// TODO(https://github.com/dart-lang/ffi/issues/4): No need to use
// 'asTypedList' when Pointer operations are performant.
class Utf8 extends Struct {
class Utf8 extends Opaque {
/// Returns the length of a zero-terminated string &mdash; the number of
/// bytes before the first zero byte.
static int strlen(Pointer<Utf8> string) {
Expand Down Expand Up @@ -54,16 +54,13 @@ class Utf8 extends Struct {
/// as replacement characters (U+FFFD, encoded as the bytes 0xEF 0xBF 0xBD)
/// in the UTF-8 encoded result. See [Utf8Encoder] for details on encoding.
///
/// Returns a malloc-allocated pointer to the result.
static Pointer<Utf8> toUtf8(String string) {
/// Returns a [allocator]-allocated pointer to the result.
static Pointer<Utf8> toUtf8(String string, {Allocator allocator = calloc}) {
final units = utf8.encode(string);
final Pointer<Uint8> result = allocate<Uint8>(count: units.length + 1);
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();
}

@override
String toString() => fromUtf8(addressOf);
}
4 changes: 2 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
name: ffi
version: 0.2.0-nullsafety.1
version: 0.3.0-nullsafety.0
homepage: https://github.com/dart-lang/ffi
description: Utilities for working with Foreign Function Interface (FFI) code.

environment:
sdk: '>=2.12.0-0 <3.0.0'
sdk: '>=2.12.0-265.0.dev <3.0.0'

# dependencies:

Expand Down
4 changes: 2 additions & 2 deletions test/utf16_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ void main() {
final Uint16List end = converted.asTypedList(start.codeUnits.length + 1);
final matcher = equals(start.codeUnits.toList()..add(0));
expect(end, matcher);
free(converted);
calloc.free(converted);
});

test('toUtf16 emoji', () {
Expand All @@ -25,6 +25,6 @@ void main() {
final Uint16List end = converted.cast<Uint16>().asTypedList(length + 1);
final matcher = equals(start.codeUnits.toList()..add(0));
expect(end, matcher);
free(converted);
calloc.free(converted);
});
}
6 changes: 3 additions & 3 deletions test/utf8_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import 'package:ffi/ffi.dart';
import 'package:test/test.dart';

Pointer<Uint8> _bytesFromList(List<int> ints) {
final Pointer<Uint8> ptr = allocate(count: ints.length);
final Pointer<Uint8> ptr = calloc(ints.length);
final Uint8List list = ptr.asTypedList(ints.length);
list.setAll(0, ints);
return ptr;
Expand All @@ -23,7 +23,7 @@ void main() {
final matcher =
equals([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10, 0]);
expect(end, matcher);
free(converted);
calloc.free(converted);
});

test('fromUtf8 ASCII', () {
Expand All @@ -41,7 +41,7 @@ void main() {
final matcher =
equals([240, 159, 152, 142, 240, 159, 145, 191, 240, 159, 146, 172, 0]);
expect(end, matcher);
free(converted);
calloc.free(converted);
});

test('formUtf8 emoji', () {
Expand Down