0.3.0-dev.0 Allocator and Opaque (#72)
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.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 47849bd..ba90e5c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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()`.
diff --git a/example/main.dart b/example/main.dart
index b00ccbb..7e25bf4 100644
--- a/example/main.dart
+++ b/example/main.dart
@@ -3,16 +3,16 @@
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);
}
diff --git a/lib/ffi.dart b/lib/ffi.dart
index 4f7e546..661a27b 100644
--- a/lib/ffi.dart
+++ b/lib/ffi.dart
@@ -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;
diff --git a/lib/src/allocation.dart b/lib/src/allocation.dart
index 9963760..e27e64e 100644
--- a/lib/src/allocation.dart
+++ b/lib/src/allocation.dart
@@ -15,6 +15,11 @@
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 =
@@ -36,43 +41,135 @@
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.
///
-/// Throws an ArgumentError on failure to free.
+/// 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.
///
-// 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.');
+/// 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 `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();
}
- } else {
- posixFree(pointer);
+ 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 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();
diff --git a/lib/src/utf16.dart b/lib/src/utf16.dart
index b63d383..fb35b3a 100644
--- a/lib/src/utf16.dart
+++ b/lib/src/utf16.dart
@@ -12,17 +12,17 @@
///
/// [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;
diff --git a/lib/src/utf8.dart b/lib/src/utf8.dart
index cf44f06..3360922 100644
--- a/lib/src/utf8.dart
+++ b/lib/src/utf8.dart
@@ -20,7 +20,7 @@
//
// 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 — the number of
/// bytes before the first zero byte.
static int strlen(Pointer<Utf8> string) {
@@ -54,16 +54,13 @@
/// 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);
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 5d7af25..5a91bb9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -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:
diff --git a/test/utf16_test.dart b/test/utf16_test.dart
index db1e361..f0935a1 100644
--- a/test/utf16_test.dart
+++ b/test/utf16_test.dart
@@ -15,7 +15,7 @@
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', () {
@@ -25,6 +25,6 @@
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);
});
}
diff --git a/test/utf8_test.dart b/test/utf8_test.dart
index d8d91ec..d4fc8db 100644
--- a/test/utf8_test.dart
+++ b/test/utf8_test.dart
@@ -9,7 +9,7 @@
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;
@@ -23,7 +23,7 @@
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', () {
@@ -41,7 +41,7 @@
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', () {