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` 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 'dart:ffi'; import 'package:ffi/ffi.dart'; void main() { - // Allocate and free some native memory with malloc and free. - final pointer = allocate(); + // Allocate and free some native memory with calloc and free. + final pointer = calloc(); 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 charPointer = Utf8.toUtf8(myString); print('First byte is: ${charPointer.cast().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 @@ typedef PosixMalloc = Pointer Function(int); final PosixMalloc posixMalloc = stdlib.lookupFunction('malloc'); +typedef PosixCallocNative = Pointer Function(IntPtr num, IntPtr size); +typedef PosixCalloc = Pointer Function(int num, int size); +final PosixCalloc posixCalloc = + stdlib.lookupFunction('calloc'); + typedef PosixFreeNative = Void Function(Pointer); typedef PosixFree = void Function(Pointer); final PosixFree posixFree = @@ -36,43 +41,135 @@ typedef WinHeapFree = int Function(Pointer heap, int flags, Pointer memory); final WinHeapFree winHeapFree = stdlib.lookupFunction('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 allocate({int count = 1}) { - final int totalSize = count * sizeOf(); - Pointer 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 allocate(int byteCount, {int? alignment}) { + Pointer 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 allocate(int byteCount, {int? alignment}) { + Pointer 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(); 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 @@ import 'package:ffi/ffi.dart'; /// /// [Utf16] is represented as a struct so that `Pointer` 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 toUtf16(String string) { + /// Returns a [allocator]-allocated pointer to the result. + static Pointer toUtf16(String string, {Allocator allocator = calloc}) { final units = string.codeUnits; - final Pointer result = allocate(count: units.length + 1); + final Pointer result = allocator(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 @@ final int _maxSize = sizeOf() == 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 — the number of /// bytes before the first zero byte. static int strlen(Pointer string) { @@ -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 toUtf8(String string) { + /// Returns a [allocator]-allocated pointer to the result. + static Pointer toUtf8(String string, {Allocator allocator = calloc}) { final units = utf8.encode(string); - final Pointer result = allocate(count: units.length + 1); + final Pointer result = allocator(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 @@ 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', () { @@ -25,6 +25,6 @@ void main() { final Uint16List end = converted.cast().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:ffi/ffi.dart'; import 'package:test/test.dart'; Pointer _bytesFromList(List ints) { - final Pointer ptr = allocate(count: ints.length); + final Pointer ptr = calloc(ints.length); final Uint8List list = ptr.asTypedList(ints.length); list.setAll(0, ints); return ptr; @@ -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', () { @@ -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', () {