Change Utf8 and Ut16 interfaces to extension methods (#83)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b6135f4..3e69db0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog
+## 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.
diff --git a/example/main.dart b/example/main.dart
index 7e25bf4..27623f0 100644
--- a/example/main.dart
+++ b/example/main.dart
@@ -11,8 +11,8 @@
// Use the Utf8 helper to encode zero-terminated UTF-8 strings in native memory.
final String myString = 'ππΏπ¬';
- final Pointer<Utf8> charPointer = Utf8.toUtf8(myString);
+ final Pointer<Utf8> charPointer = myString.toNativeUtf8();
print('First byte is: ${charPointer.cast<Uint8>().value}');
- print(Utf8.fromUtf8(charPointer));
+ print(charPointer.toDartString());
calloc.free(charPointer);
}
diff --git a/lib/src/utf16.dart b/lib/src/utf16.dart
index fb35b3a..468e32a 100644
--- a/lib/src/utf16.dart
+++ b/lib/src/utf16.dart
@@ -7,24 +7,88 @@
import 'package:ffi/ffi.dart';
-/// [Utf16] implements conversion between Dart strings and zero-terminated
-/// UTF-16 encoded "char*" strings in C.
+/// The contents of a native zero-terminated array of UTF-16 code units.
///
-/// [Utf16] is represented as a struct so that `Pointer<Utf16>` can be used in
-/// native function signatures.
+/// 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 {
- /// Convert a [String] to a UTF-16 encoded zero-terminated C string.
+ /// Creates a zero-terminated [Utf16] code-unit array from [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.
+ /// If [string] contains NUL characters, the converted string will be truncated
+ /// prematurely.
///
- /// Returns a [allocator]-allocated pointer to the result.
+ /// Returns an [allocator]-allocated pointer to the result.
+ @Deprecated('Use StringUtf16Pointer.toNativeUtf16 instead.')
static Pointer<Utf16> toUtf16(String string, {Allocator allocator = calloc}) {
- final units = string.codeUnits;
+ return string.toNativeUtf16(allocator: allocator);
+ }
+}
+
+/// 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.
+ ///
+ /// 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.
+ ///
+ /// 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 02a5eaa..4683d3e 100644
--- a/lib/src/utf8.dart
+++ b/lib/src/utf8.dart
@@ -8,19 +8,56 @@
import 'package:ffi/ffi.dart';
-/// [Utf8] implements conversion between Dart strings and zero-terminated
-/// UTF-8 encoded "char*" strings in C.
+/// The contents of a native zero-terminated array of UTF-8 code units.
///
-/// [Utf8] is represented 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.
+/// 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 {
- /// Returns the length of a zero-terminated string — the number of
- /// bytes before the first zero byte.
+ /// 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 bytes up to the
+ /// first zero byte.
+ @Deprecated('Use Utf8Pointer.length instead.')
static int strlen(Pointer<Utf8> string) {
- final Pointer<Uint8> array = string.cast<Uint8>();
+ return string.length;
+ }
+
+ /// Converts the UTF-8 encoded [string] to a Dart string.
+ ///
+ /// 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.
+ ///
+ /// If [length] is provided, zero-termination is ignored and the result can
+ /// contain NUL characters.
+ @Deprecated('Use Utf8Pointer.toDartString instead.')
+ static String fromUtf8(Pointer<Utf8> string, {int? length}) {
+ return string.toDartString(length: length);
+ }
+
+ /// Creates a zero-terminated [Utf8] code-unit array from [string].
+ ///
+ /// If [string] contains NUL characters, the converted string will be truncated
+ /// prematurely. Unpaired surrogate code points in [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.
+ @Deprecated('Use StringUtf8Pointer.toNativeUtf8 instead.')
+ static Pointer<Utf8> toUtf8(String string, {Allocator allocator = calloc}) {
+ return string.toNativeUtf8(allocator: allocator);
+ }
+}
+
+/// 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++;
@@ -28,34 +65,39 @@
return length;
}
- /// Creates a [String] containing the characters UTF-8 encoded in [string].
+ /// Converts this UTF-8 encoded string to a Dart string.
///
- /// Either the [string] must be zero-terminated or its [length] — the
- /// number of bytes — must be specified as a non-negative value. The
- /// byte sequence must be valid UTF-8 encodings of Unicode scalar values. A
- /// [FormatException] is thrown if the input is malformed. 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, {int? 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 = strlen(string);
+ length = this.length;
}
- return utf8.decode(string.cast<Uint8>().asTypedList(length));
+ return utf8.decode(cast<Uint8>().asTypedList(length));
}
+}
- /// Convert a [String] to a UTF-8 encoded zero-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 characters, the converted string will be truncated
- /// prematurely. Unpaired surrogate code points in [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.
+ /// 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 [allocator]-allocated pointer to the result.
- static Pointer<Utf8> toUtf8(String string, {Allocator allocator = calloc}) {
- final units = utf8.encode(string);
+ /// 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);
diff --git a/pubspec.yaml b/pubspec.yaml
index afd19d6..e8a388c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: ffi
-version: 0.3.0-nullsafety.3
+version: 0.3.1-nullsafety.0
homepage: https://github.com/dart-lang/ffi
description: Utilities for working with Foreign Function Interface (FFI) code.
diff --git a/test/utf16_test.dart b/test/utf16_test.dart
index f0935a1..615056a 100644
--- a/test/utf16_test.dart
+++ b/test/utf16_test.dart
@@ -11,7 +11,7 @@
void main() {
test('toUtf16 ASCII', () {
final String start = 'Hello World!\n';
- final Pointer<Uint16> converted = Utf16.toUtf16(start).cast();
+ 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);
@@ -20,11 +20,47 @@
test('toUtf16 emoji', () {
final String start = 'π';
- final Pointer<Utf16> converted = Utf16.toUtf16(start).cast();
+ 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);
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 d4fc8db..9c0486a 100644
--- a/test/utf8_test.dart
+++ b/test/utf8_test.dart
@@ -18,7 +18,7 @@
void main() {
test('toUtf8 ASCII', () {
final String start = 'Hello World!\n';
- final Pointer<Uint8> converted = Utf8.toUtf8(start).cast();
+ 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]);
@@ -29,14 +29,14 @@
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);
+ 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);
+ 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]);
@@ -47,46 +47,53 @@
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);
+ final String end = utf8.toDartString();
expect(end, 'ππΏπ¬');
});
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.fromUtf8(utf8, length: 5);
+ 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.fromUtf8(utf8, length: 4);
+ 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.fromUtf8(utf8, length: 0);
+ 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.fromUtf8(utf8, length: -1), throwsRangeError);
+ 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.fromUtf8(utf8, length: 13);
+ 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);
+ });
}