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

Commit

Permalink
0.3.0-dev.0 Allocator and Opaque (#72)
Browse files Browse the repository at this point in the history
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](dart-lang/sdk#44622) for more info.

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

This pre-release requires Dart `2.12.0-265.0.dev` or greater.
  • Loading branch information
dcharkes authored Feb 1, 2021
1 parent 3135297 commit b072465
Show file tree
Hide file tree
Showing 9 changed files with 157 additions and 51 deletions.
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

0 comments on commit b072465

Please sign in to comment.