-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(windows_foundation): add wrapper for
HSTRING
(#324)
- Loading branch information
1 parent
a2094b3
commit d8e3533
Showing
3 changed files
with
198 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
// Copyright (c) 2023, Dart | Windows. Please see the AUTHORS file 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. | ||
|
||
// A wrapper for HSTRING types. | ||
|
||
import 'dart:ffi'; | ||
|
||
import 'package:ffi/ffi.dart'; | ||
import 'package:win32/win32.dart'; | ||
|
||
import '../internal.dart'; | ||
|
||
/// A string data type commonly used by Windows Runtime. | ||
/// | ||
/// This class wraps the Windows Runtime memory allocation functions, allowing | ||
/// the creation of [HSTRING] types with ease, as shown below: | ||
/// ```dart | ||
/// final hString = HString.fromString('Hello world!'); | ||
/// ``` | ||
/// | ||
/// In general, freeing the memory allocated for a [HString] when it is no | ||
/// longer used is not a concern, as the [Finalizer] automatically releases | ||
/// it for you when the object becomes inaccessible to the program. However, | ||
/// you also have the option to manually release its memory by calling the | ||
/// object's [free] method. | ||
class HString { | ||
/// The handle to a [HSTRING]. | ||
final int handle; | ||
|
||
/// Create an empty [HString]. | ||
// There is no need to attach a finalizer to an empty HString, as it does not | ||
// need to be freed. | ||
const HString.empty() : handle = 0; | ||
|
||
HString._(this.handle) { | ||
_finalizer.attach(this, handle, detach: this); | ||
} | ||
|
||
static final _finalizer = Finalizer<int>(WindowsDeleteString); | ||
|
||
/// Create a [HString] from a given HSTRING [handle]. | ||
factory HString.fromHandle(int handle) => | ||
handle == 0 ? const HString.empty() : HString._(handle); | ||
|
||
/// Create a [HString] from a given Dart [string]. | ||
factory HString.fromString(String string) { | ||
if (string.isEmpty) return const HString.empty(); | ||
return using((arena) { | ||
final pSourceString = string.toNativeUtf16(allocator: arena); | ||
final pString = arena<HSTRING>(); | ||
final hr = WindowsCreateString(pSourceString, string.length, pString); | ||
if (FAILED(hr)) throwWindowsException(hr); | ||
return HString._(pString.value); | ||
}); | ||
} | ||
|
||
/// Creates a copy of the existing string. | ||
HString clone() { | ||
if (handle == 0) return const HString.empty(); | ||
return using((arena) { | ||
final pNewString = arena<HSTRING>(); | ||
final hr = WindowsDuplicateString(handle, pNewString); | ||
if (FAILED(hr)) throwWindowsException(hr); | ||
return HString._(pNewString.value); | ||
}); | ||
} | ||
|
||
/// Releases the native memory allocated to the [HString]. | ||
void free() { | ||
_finalizer.detach(this); | ||
WindowsDeleteString(handle); | ||
} | ||
|
||
/// Whether the string is empty. | ||
bool get isEmpty => handle == 0 ? true : WindowsIsStringEmpty(handle) == TRUE; | ||
|
||
/// The length of the string. | ||
int get length => handle == 0 ? 0 : WindowsGetStringLen(handle); | ||
|
||
/// Concatenates two [HString]s. | ||
HString operator +(HString other) { | ||
return using((arena) { | ||
final pNewString = arena<HSTRING>(); | ||
final hr = WindowsConcatString(handle, other.handle, pNewString); | ||
if (FAILED(hr)) throwWindowsException(hr); | ||
return HString._(pNewString.value); | ||
}); | ||
} | ||
|
||
/// Converts the [HString] into a regular Dart string. | ||
@override | ||
String toString() => handle == 0 | ||
? '' | ||
: WindowsGetStringRawBuffer(handle, nullptr).toDartString(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// Copyright (c) 2023, Dart | Windows. Please see the AUTHORS file 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. | ||
|
||
@TestOn('windows') | ||
|
||
import 'package:test/test.dart'; | ||
import 'package:win32/win32.dart'; | ||
import 'package:windows_foundation/windows_foundation.dart'; | ||
|
||
void main() { | ||
if (!isWindowsRuntimeAvailable()) { | ||
print('Skipping tests because Windows Runtime is not available.'); | ||
return; | ||
} | ||
|
||
const testRuns = 100; | ||
|
||
group('HString', () { | ||
test('allocation', () { | ||
const testString = 'Hello world!'; | ||
for (var i = 0; i < testRuns; i++) { | ||
final hString = HString.fromString(testString); | ||
expect(hString.toString(), equals(testString)); | ||
hString.free(); | ||
} | ||
}); | ||
|
||
test('allocation of long strings', () { | ||
final longString = 'A very long string with padding.' * 65536; | ||
// Ten allocations is probably enough for an expensive test like this. | ||
for (var i = 0; i < 10; i++) { | ||
final hString = HString.fromString(longString); | ||
expect(hString.toString(), equals(longString)); | ||
hString.free(); | ||
} | ||
}); | ||
|
||
test('clone', () { | ||
const testString1 = 'This message is not unique.'; | ||
const testString2 = ''; | ||
for (var i = 0; i < testRuns; i++) { | ||
for (final str in [testString1, testString2]) { | ||
final original = HString.fromString(str); | ||
final clone = original.clone(); | ||
expect(original.toString(), equals(clone.toString())); | ||
expect(original.handle, equals(clone.handle)); | ||
clone.free(); | ||
original.free(); | ||
} | ||
} | ||
}); | ||
|
||
test('concatenation', () { | ||
for (var i = 0; i < testRuns; i++) { | ||
final first = HString.fromString('Windows'); | ||
final second = HString.fromString(' and Dart'); | ||
final matchInHeaven = first + second; | ||
expect(matchInHeaven.toString(), equals('Windows and Dart')); | ||
[first, second, matchInHeaven].forEach((object) => object.free()); | ||
} | ||
}); | ||
|
||
test('empty', () { | ||
for (var i = 0; i < testRuns; i++) { | ||
final hString = const HString.empty(); | ||
expect(hString.isEmpty, isTrue); | ||
expect(hString.toString(), isEmpty); | ||
hString.free(); | ||
} | ||
}); | ||
|
||
test('isEmpty', () { | ||
const testString = 'dartwinrt'; | ||
for (var i = 0; i < testRuns; i++) { | ||
final hString1 = HString.fromString(testString); | ||
expect(hString1.isEmpty, isFalse); | ||
final hString2 = const HString.empty(); | ||
expect(hString2.isEmpty, isTrue); | ||
final hString3 = HString.fromString(''); | ||
expect(hString3.isEmpty, isTrue); | ||
hString1.free(); | ||
hString2.free(); | ||
hString3.free(); | ||
} | ||
}); | ||
|
||
test('length', () { | ||
const testString = 'Lorem ipsum dolor sit amet, consectetur adipiscing ' | ||
'elit, sed do eiusmod tempor incididunt ut labore et dolore magna ' | ||
'aliqua.'; | ||
for (var i = 0; i < testRuns; i++) { | ||
final hString = HString.fromString(testString); | ||
expect(testString.length, equals(123)); | ||
expect(hString.length, equals(123)); | ||
expect(hString.toString(), equals(testString)); | ||
hString.free(); | ||
} | ||
}); | ||
}); | ||
} |