Merge commit '454ab0f' to master
This commit was used in the DEPS for a while, but was lost when the
branch was deleted.
See https://github.com/flutter/flutter/issues/76952.
diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml
new file mode 100644
index 0000000..1cd8438
--- /dev/null
+++ b/.github/workflows/test-package.yml
@@ -0,0 +1,61 @@
+name: Dart CI
+
+on:
+ # Run on PRs and pushes to the default branch.
+ push:
+ branches: [ master ]
+ pull_request:
+ branches: [ master ]
+ schedule:
+ - cron: "0 0 * * 0"
+
+env:
+ PUB_ENVIRONMENT: bot.github
+
+jobs:
+ # Check code formatting and static analysis on a single OS (linux)
+ # against Dart dev.
+ analyze:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@v2
+ - uses: dart-lang/setup-dart@v0.1
+ with:
+ channel: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Check formatting
+ run: dart format --output=none --set-exit-if-changed .
+ if: always() && steps.install.outcome == 'success'
+ - name: Analyze code
+ run: dart analyze --fatal-infos
+ if: always() && steps.install.outcome == 'success'
+
+ # Run tests on a matrix consisting of two dimensions:
+ # 1. OS: ubuntu-latest, (macos-latest, windows-latest)
+ # 2. release channel: dev
+ test:
+ needs: analyze
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ # Add macos-latest and/or windows-latest if relevant for this package.
+ os: [ubuntu-latest]
+ sdk: [dev]
+ steps:
+ - uses: actions/checkout@v2
+ - uses: dart-lang/setup-dart@v0.1
+ with:
+ channel: ${{ matrix.sdk }}
+ - id: install
+ name: Install dependencies
+ run: dart pub get
+ - name: Run VM tests
+ run: dart test --platform vm
+ if: always() && steps.install.outcome == 'success'
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 6ec14e5..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-language: dart
-dart:
-- dev
-# Only building master means that we don't run two builds for each pull request.
-dart_task:
-- test: --platform vm
-- dartanalyzer
-- dartfmt
-branches:
- only: [master]
-cache:
- directories:
- - $HOME/.pub-cache
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1d335e8..8d404e9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,53 @@
# Changelog
+## 1.0.0
+
+Bumping the version of this package to `1.0.0`.
+
+Removes all deprecated methods, use `0.3.0-nullsafety.3` for migration.
+
+## 0.3.1-nullsafety.0
+
+Deprecates the static methods on `Utf8` and `Utf16` and introduces
+extension methods to replace them.
+
+## 0.3.0-nullsafety.3
+
+Adds back in deprecated `allocate` and `free` to ease migration.
+These will be removed in the next release.
+
+This pre-release requires Dart `2.12.0-259.9.beta` or greater.
+
+## 0.3.0-nullsafety.1
+
+This pre-release requires Dart `2.12.0-259.8.beta` or greater.
+
+Note that this pre-release does _not_ work in Flutter versions containing Dart
+`2.12.0-260.0.dev` - `2.12.0-264.0.dev`.
+Using `Allocator.call` throws a `NoSuchMethodError` in these versions.
+See [Flutter Engine #23954](https://github.com/flutter/engine/pull/23954) for more info.
+
+## 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()`.
+
+## 0.2.0-nullsafety.0
+
+Pre-release (non-stable) release supporting null safety.
+Requires Dart 2.12.0 or greater.
+
## 0.1.3
Stable release incorporating all the previous dev release changes.
diff --git a/README.md b/README.md
index 48e5873..63c3dd3 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-[![Build Status](https://travis-ci.org/dart-lang/ffi.svg?branch=master)](https://travis-ci.org/dart-lang/ffi)
+[![Build Status](https://github.com/dart-lang/ffi/workflows/Dart%20CI/badge.svg)](https://github.com/dart-lang/ffi/actions?query=workflow%3A"Dart+CI")
Utilities for working with Foreign Function Interface (FFI) code, incl.
-converting between Dart strings and C strings encoded with utf8 and utf16.
+converting between Dart strings and C strings encoded with UTF-8 and UTF-16.
For additional details about Dart FFI (`dart:ffi`), see
https://dart.dev/guides/libraries/c-interop.
diff --git a/analysis_options.yaml b/analysis_options.yaml
index 108d105..a5b0738 100644
--- a/analysis_options.yaml
+++ b/analysis_options.yaml
@@ -1 +1,5 @@
include: package:pedantic/analysis_options.yaml
+
+linter:
+ rules:
+ omit_local_variable_types: false
diff --git a/example/main.dart b/example/main.dart
index 352f834..27623f0 100644
--- a/example/main.dart
+++ b/example/main.dart
@@ -2,17 +2,17 @@
import 'package:ffi/ffi.dart';
-main() {
- // Allocate and free some native memory with malloc and free.
- final pointer = allocate<Uint8>();
+void main() {
+ // 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 null-terminated Utf8 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);
+ // Use the Utf8 helper to encode zero-terminated UTF-8 strings in native memory.
+ final String myString = 'ππΏπ¬';
+ final Pointer<Utf8> charPointer = myString.toNativeUtf8();
+ print('First byte is: ${charPointer.cast<Uint8>().value}');
+ print(charPointer.toDartString());
+ 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 c1b7277..e27e64e 100644
--- a/lib/src/allocation.dart
+++ b/lib/src/allocation.dart
@@ -7,72 +7,169 @@
// Note that kernel32.dll is the correct name in both 32-bit and 64-bit.
final DynamicLibrary stdlib = Platform.isWindows
- ? DynamicLibrary.open("kernel32.dll")
+ ? DynamicLibrary.open('kernel32.dll')
: DynamicLibrary.process();
typedef PosixMallocNative = Pointer Function(IntPtr);
typedef PosixMalloc = Pointer Function(int);
final PosixMalloc posixMalloc =
- stdlib.lookupFunction<PosixMallocNative, PosixMalloc>("malloc");
+ 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 =
- stdlib.lookupFunction<PosixFreeNative, PosixFree>("free");
+ stdlib.lookupFunction<PosixFreeNative, PosixFree>('free');
typedef WinGetProcessHeapFn = Pointer Function();
final WinGetProcessHeapFn winGetProcessHeap = stdlib
- .lookupFunction<WinGetProcessHeapFn, WinGetProcessHeapFn>("GetProcessHeap");
+ .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");
+ 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");
+ stdlib.lookupFunction<WinHeapFreeNative, WinHeapFree>('HeapFree');
-/// 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}) {
- final int totalSize = count * sizeOf<T>();
- Pointer<T> result;
- if (Platform.isWindows) {
- result = winHeapAlloc(processHeap, /*flags=*/ 0, totalSize).cast();
- } else {
- result = posixMalloc(totalSize).cast();
- }
- if (result.address == 0) {
- throw ArgumentError("Could not allocate $totalSize bytes.");
- }
- return result;
-}
+const int HEAP_ZERO_MEMORY = 8;
-/// 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 [_CallocAllocator]
+/// for zero-initialized memory on allocation.
///
-/// Throws an ArgumentError on failure to free.
-///
-// 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 `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();
}
- } 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.
+///
+/// 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.
+///
+/// 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();
+ }
+ 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 f1b64fa..f5bea5b 100644
--- a/lib/src/utf16.dart
+++ b/lib/src/utf16.dart
@@ -2,30 +2,82 @@
// 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';
-/// [Utf16] implements conversion between Dart strings and null-terminated
-/// UTF-16 encoded "char*" strings in C.
+/// The contents of a native zero-terminated array of UTF-16 code units.
///
-/// [Utf16] is respresented as a struct so that `Pointer<Utf16>` can be used in
-/// native function signatures.
-class Utf16 extends Struct {
- /// Convert a [String] to a Utf16-encoded null-terminated C string.
+/// The Utf16 type itself has no functionality, it's only intended to be used
+/// through a `Pointer<Utf16>` representing the entire array. This pointer is
+/// the equivalent of a char pointer (`const wchar_t*`) in C code. The
+/// individual UTF-16 code units are stored in native byte order.
+class Utf16 extends Opaque {}
+
+/// Extension method for converting a`Pointer<Utf16>` to a [String].
+extension Utf16Pointer on Pointer<Utf16> {
+ /// The number of UTF-16 code units in this zero-terminated UTF-16 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.
+ /// The UTF-16 code units of the strings are the non-zero code units up to
+ /// the first zero code unit.
+ int get length {
+ final Pointer<Uint16> array = cast<Uint16>();
+ int length = 0;
+ while (array[length] != 0) {
+ length++;
+ }
+ return length;
+ }
+
+ /// Converts this UTF-16 encoded string to a Dart string.
///
- /// Returns a malloc-allocated pointer to the result.
- static Pointer<Utf16> toUtf16(String s) {
- final units = s.codeUnits;
- final Pointer<Uint16> result = allocate<Uint16>(count: units.length + 1);
+ /// Decodes the UTF-16 code units of this zero-terminated code unit array as
+ /// Unicode code points and creates a Dart string containing those code
+ /// points.
+ ///
+ /// If [length] is provided, zero-termination is ignored and the result can
+ /// contain NUL characters.
+ String toDartString({int? length}) {
+ if (length == null) {
+ return _toUnknownLengthString(cast<Uint16>());
+ } else {
+ RangeError.checkNotNegative(length, 'length');
+ return _toKnownLengthString(cast<Uint16>(), length);
+ }
+ }
+
+ static String _toKnownLengthString(Pointer<Uint16> codeUnits, int length) =>
+ String.fromCharCodes(codeUnits.asTypedList(length));
+
+ static String _toUnknownLengthString(Pointer<Uint16> codeUnits) {
+ final buffer = StringBuffer();
+ var i = 0;
+ while (true) {
+ final char = codeUnits.elementAt(i).value;
+ if (char == 0) {
+ return buffer.toString();
+ }
+ buffer.writeCharCode(char);
+ i++;
+ }
+ }
+}
+
+/// Extension method for converting a [String] to a `Pointer<Utf16>`.
+extension StringUtf16Pointer on String {
+ /// Creates a zero-terminated [Utf16] code-unit array from this String.
+ ///
+ /// If this [String] contains NUL characters, converting it back to a string
+ /// using [Utf16Pointer.toDartString] will truncate the result if a length is
+ /// not passed.
+ ///
+ /// Returns an [allocator]-allocated pointer to the result.
+ Pointer<Utf16> toNativeUtf16({Allocator allocator = malloc}) {
+ final units = codeUnits;
+ final Pointer<Uint16> result = allocator<Uint16>(units.length + 1);
final Uint16List nativeString = result.asTypedList(units.length + 1);
- nativeString.setAll(0, units);
+ nativeString.setRange(0, units.length, units);
nativeString[units.length] = 0;
return result.cast();
}
diff --git a/lib/src/utf8.dart b/lib/src/utf8.dart
index df8f1f2..04dd7a7 100644
--- a/lib/src/utf8.dart
+++ b/lib/src/utf8.dart
@@ -8,57 +8,65 @@
import 'package:ffi/ffi.dart';
-const int _kMaxSmi64 = (1 << 62) - 1;
-const int _kMaxSmi32 = (1 << 30) - 1;
-final int _maxSize = sizeOf<IntPtr>() == 8 ? _kMaxSmi64 : _kMaxSmi32;
-
-/// [Utf8] implements conversion between Dart strings and null-terminated
-/// Utf8-encoded "char*" strings in C.
+/// The contents of a native zero-terminated array of UTF-8 code units.
///
-/// [Utf8] is respresented as a struct so that `Pointer<Utf8>` can be used in
-/// native function signatures.
-//
-// TODO(https://github.com/dart-lang/ffi/issues/4): No need to use
-// 'asTypedList' when Pointer operations are performant.
-class Utf8 extends Struct {
- /// Returns the length of a null-terminated string -- the number of (one-byte)
- /// characters before the first null byte.
- static int strlen(Pointer<Utf8> string) {
- final Pointer<Uint8> array = string.cast<Uint8>();
- final Uint8List nativeString = array.asTypedList(_maxSize);
- return nativeString.indexWhere((char) => char == 0);
+/// The Utf8 type itself has no functionality, it's only intended to be used
+/// through a `Pointer<Utf8>` representing the entire array. This pointer is
+/// the equivalent of a char pointer (`const char*`) in C code.
+class Utf8 extends Opaque {}
+
+/// Extension method for converting a`Pointer<Utf8>` to a [String].
+extension Utf8Pointer on Pointer<Utf8> {
+ /// The number of UTF-8 code units in this zero-terminated UTF-8 string.
+ ///
+ /// The UTF-8 code units of the strings are the non-zero code units up to the
+ /// first zero code unit.
+ int get length {
+ final Pointer<Uint8> array = cast<Uint8>();
+ int length = 0;
+ while (array[length] != 0) {
+ length++;
+ }
+ return length;
}
- /// Creates a [String] containing the characters UTF-8 encoded in [string].
+ /// Converts this UTF-8 encoded string to a Dart string.
///
- /// The [string] 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.
+ /// Decodes the UTF-8 code units of this zero-terminated byte array as
+ /// Unicode code points and creates a Dart string containing those code
+ /// points.
///
- /// Returns a Dart string containing the decoded code points.
- static String fromUtf8(Pointer<Utf8> string) {
- final int length = strlen(string);
- return utf8.decode(Uint8List.view(
- string.cast<Uint8>().asTypedList(length).buffer, 0, length));
+ /// If [length] is provided, zero-termination is ignored and the result can
+ /// contain NUL characters.
+ String toDartString({int? length}) {
+ if (length != null) {
+ RangeError.checkNotNegative(length, 'length');
+ } else {
+ length = this.length;
+ }
+ return utf8.decode(cast<Uint8>().asTypedList(length));
}
+}
- /// Convert a [String] to a Utf8-encoded null-terminated C string.
+/// Extension method for converting a [String] to a `Pointer<Utf8>`.
+extension StringUtf8Pointer on String {
+ /// Creates a zero-terminated [Utf8] code-unit array from this 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.
+ /// If this [String] contains NUL characters, converting it back to a string
+ /// using [Utf8Pointer.toDartString] will truncate the result if a length is
+ /// not passed.
///
- /// Returns a malloc-allocated pointer to the result.
- static Pointer<Utf8> toUtf8(String string) {
- final units = utf8.encode(string);
- final Pointer<Uint8> result = allocate<Uint8>(count: units.length + 1);
+ /// Unpaired surrogate code points in this [String] will be encoded 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 an [allocator]-allocated pointer to the result.
+ Pointer<Utf8> toNativeUtf8({Allocator allocator = malloc}) {
+ 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();
}
-
- String toString() => fromUtf8(addressOf);
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 25a3aeb..d2daa1b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,13 +1,11 @@
name: ffi
-version: 0.1.3+1
+version: 1.0.0
homepage: https://github.com/dart-lang/ffi
description: Utilities for working with Foreign Function Interface (FFI) code.
environment:
- sdk: '>=2.9.0 <3.0.0'
-
-# dependencies:
+ sdk: '>=2.12.0-259.9.beta <3.0.0'
dev_dependencies:
- pedantic: ^1.0.0
- test: ^1.6.8
+ pedantic: ^1.10.0
+ test: ^1.16.0
diff --git a/test/utf16_test.dart b/test/utf16_test.dart
index f55cb3b..615056a 100644
--- a/test/utf16_test.dart
+++ b/test/utf16_test.dart
@@ -5,26 +5,62 @@
import 'dart:ffi';
import 'dart:typed_data';
-import 'package:test/test.dart';
import 'package:ffi/ffi.dart';
+import 'package:test/test.dart';
-main() {
- test("toUtf16 ASCII", () {
- final String start = "Hello World!\n";
- final Pointer<Uint16> converted = Utf16.toUtf16(start).cast();
+void main() {
+ test('toUtf16 ASCII', () {
+ final String start = 'Hello World!\n';
+ final Pointer<Uint16> converted = start.toNativeUtf16().cast();
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", () {
- final String start = "π";
- final Pointer<Utf16> converted = Utf16.toUtf16(start).cast();
+ test('toUtf16 emoji', () {
+ final String start = 'π';
+ final Pointer<Utf16> converted = start.toNativeUtf16().cast();
final int length = start.codeUnits.length;
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);
+ });
+
+ test('from Utf16 ASCII', () {
+ final string = 'Hello World!\n';
+ final utf16Pointer = string.toNativeUtf16();
+ final stringAgain = utf16Pointer.toDartString();
+ expect(stringAgain, string);
+ calloc.free(utf16Pointer);
+ });
+
+ test('from Utf16 emoji', () {
+ final string = 'π';
+ final utf16Pointer = string.toNativeUtf16();
+ final stringAgain = utf16Pointer.toDartString();
+ expect(stringAgain, string);
+ calloc.free(utf16Pointer);
+ });
+
+ test('zero bytes', () {
+ final string = 'Hello\x00World!\n';
+ final utf16Pointer = string.toNativeUtf16();
+ final stringAgain = utf16Pointer.toDartString(length: 13);
+ expect(stringAgain, string);
+ calloc.free(utf16Pointer);
+ });
+
+ test('length', () {
+ final string = 'Hello';
+ final utf16Pointer = string.toNativeUtf16();
+ expect(utf16Pointer.length, 5);
+ calloc.free(utf16Pointer);
+ });
+
+ test('fromUtf8 with negative length', () {
+ final Pointer<Utf16> utf16 = Pointer.fromAddress(0);
+ expect(() => utf16.toDartString(length: -1), throwsRangeError);
});
}
diff --git a/test/utf8_test.dart b/test/utf8_test.dart
index ab673b2..9c0486a 100644
--- a/test/utf8_test.dart
+++ b/test/utf8_test.dart
@@ -5,54 +5,95 @@
import 'dart:ffi';
import 'dart:typed_data';
-import 'package:test/test.dart';
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;
}
-main() {
- test("toUtf8 ASCII", () {
- final String start = "Hello World!\n";
- final Pointer<Uint8> converted = Utf8.toUtf8(start).cast();
+void main() {
+ test('toUtf8 ASCII', () {
+ final String start = 'Hello World!\n';
+ final Pointer<Uint8> converted = start.toNativeUtf8().cast();
final Uint8List end = converted.asTypedList(start.length + 1);
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", () {
+ test('fromUtf8 ASCII', () {
final Pointer<Utf8> utf8 = _bytesFromList(
[72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10, 0]).cast();
- final String end = Utf8.fromUtf8(utf8);
- expect(end, "Hello World!\n");
+ final String end = utf8.toDartString();
+ expect(end, 'Hello World!\n');
});
- test("toUtf8 emoji", () {
- final String start = "ππΏπ¬";
- final Pointer<Utf8> converted = Utf8.toUtf8(start).cast();
- final int length = Utf8.strlen(converted);
+ test('toUtf8 emoji', () {
+ final String start = 'ππΏπ¬';
+ final Pointer<Utf8> converted = start.toNativeUtf8().cast();
+ final int length = converted.length;
final Uint8List end = converted.cast<Uint8>().asTypedList(length + 1);
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", () {
+ test('formUtf8 emoji', () {
final Pointer<Utf8> utf8 = _bytesFromList(
[240, 159, 152, 142, 240, 159, 145, 191, 240, 159, 146, 172, 0]).cast();
- final String end = Utf8.fromUtf8(utf8);
- expect(end, "ππΏπ¬");
+ final String end = utf8.toDartString();
+ expect(end, 'ππΏπ¬');
});
- test("fromUtf8 invalid", () {
+ test('fromUtf8 invalid', () {
final Pointer<Utf8> utf8 = _bytesFromList([0x80, 0x00]).cast();
- expect(() => Utf8.fromUtf8(utf8), throwsA(isFormatException));
+ expect(() => utf8.toDartString(), throwsA(isFormatException));
+ });
+
+ test('fromUtf8 ASCII with length', () {
+ final Pointer<Utf8> utf8 = _bytesFromList(
+ [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10, 0]).cast();
+ final String end = utf8.toDartString(length: 5);
+ expect(end, 'Hello');
+ });
+
+ test('fromUtf8 emoji with length', () {
+ final Pointer<Utf8> utf8 = _bytesFromList(
+ [240, 159, 152, 142, 240, 159, 145, 191, 240, 159, 146, 172, 0]).cast();
+ final String end = utf8.toDartString(length: 4);
+ expect(end, 'π');
+ });
+
+ test('fromUtf8 with zero length', () {
+ final Pointer<Utf8> utf8 = _bytesFromList(
+ [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10, 0]).cast();
+ final String end = utf8.toDartString(length: 0);
+ expect(end, '');
+ });
+
+ test('fromUtf8 with negative length', () {
+ final Pointer<Utf8> utf8 = _bytesFromList(
+ [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 10, 0]).cast();
+ expect(() => utf8.toDartString(length: -1), throwsRangeError);
+ });
+
+ test('fromUtf8 with length and containing a zero byte', () {
+ final Pointer<Utf8> utf8 = _bytesFromList(
+ [72, 101, 108, 108, 111, 0, 87, 111, 114, 108, 100, 33, 10]).cast();
+ final String end = utf8.toDartString(length: 13);
+ expect(end, 'Hello\x00World!\n');
+ });
+
+ test('length', () {
+ final string = 'Hello';
+ final utf8Pointer = string.toNativeUtf8();
+ expect(utf8Pointer.length, 5);
+ calloc.free(utf8Pointer);
});
}