Skip to content

Commit

Permalink
feat(windows_foundation): add wrapper for HSTRING (#324)
Browse files Browse the repository at this point in the history
  • Loading branch information
halildurmus committed Jul 23, 2023
1 parent a2094b3 commit d8e3533
Show file tree
Hide file tree
Showing 3 changed files with 198 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/windows_foundation/lib/src/exports.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export 'collections/stringmap.dart';
export 'collections/valueset.dart';
export 'extensions/iunknown_helpers.dart';
export 'helpers.dart';
export 'hstring.dart';
export 'iclosable.dart';
export 'imemorybuffer.dart';
export 'imemorybufferreference.dart';
Expand Down
96 changes: 96 additions & 0 deletions packages/windows_foundation/lib/src/hstring.dart
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();
}
101 changes: 101 additions & 0 deletions packages/windows_foundation/test/hstring_test.dart
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();
}
});
});
}

0 comments on commit d8e3533

Please sign in to comment.