Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Tim Sneath
committed
May 3, 2022
1 parent
640c6fb
commit cc9933f
Showing
7 changed files
with
269 additions
and
12 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,82 @@ | ||
// A wrapper for BSTR string types. | ||
|
||
import 'dart:ffi'; | ||
|
||
import 'package:ffi/ffi.dart'; | ||
|
||
import 'oleaut32.dart'; | ||
|
||
/// A string data type that is commonly used by OLE Automation, as well as some | ||
/// COM methods. | ||
/// | ||
/// `BSTR` types differ from `Pointer<Utf16>` in that they include a four byte | ||
/// prefix stored immediately prior to the string itself that represents its | ||
/// length in bytes. The pointer points to the first character of the data | ||
/// string, not to the length prefix. | ||
/// | ||
/// `BSTR`s should never be created using Dart's memory allocation functions. | ||
/// For instance, the following code is incorrect, since it does not allocate | ||
/// and store the length prefix. | ||
/// | ||
/// ```dart | ||
/// final bstr = 'I am a happy BSTR'.toNativeUtf16(); | ||
/// ``` | ||
/// | ||
/// This class wraps the COM memory allocation functions so that `BSTR` types | ||
/// can be created without concern. Instead of the above code, you can write: | ||
/// | ||
/// ```dart | ||
/// final bstr = BSTR.fromString('I am a happy BSTR'); | ||
/// ``` | ||
/// | ||
/// A debugger that examines the four bytes prior to this location will see a | ||
/// 32-bit int containing the value 34, representing the length of the string in | ||
/// Utf-16. | ||
/// | ||
/// Dart does not garbage collect `BSTR` objects; instead, you are responsible | ||
/// for freeing the memory allocated for a `BSTR` when it is no longer used. To | ||
/// release its memory, you can call an object's [free] method. | ||
class BSTR { | ||
/// A pointer to the start of the string itself. | ||
/// | ||
/// The string is null terminated with a two-byte value (0x0000). | ||
final Pointer<Utf16> ptr; | ||
|
||
const BSTR._(this.ptr); | ||
|
||
/// Create a BSTR from a given Dart string. | ||
/// | ||
/// This allocates native memory for the BSTR; it can be released with [free]. | ||
factory BSTR.fromString(String str) { | ||
final pStr = str.toNativeUtf16(); | ||
final pbstr = SysAllocString(pStr); | ||
calloc.free(pStr); | ||
return BSTR._(pbstr); | ||
} | ||
|
||
/// Returns the length in characters. | ||
int get length => SysStringLen(ptr); | ||
|
||
/// Returns the length in bytes. | ||
int get byteLength => SysStringByteLen(ptr); | ||
|
||
/// Releases the native memory allocated to the BSTR. | ||
void free() => SysFreeString(ptr); | ||
|
||
/// Concatenate two BSTR objects and returns a newly-allocated object with | ||
/// the results. | ||
BSTR operator +(BSTR other) { | ||
final pbstrResult = calloc<Pointer<Utf16>>(); | ||
VarBstrCat(ptr, other.ptr, pbstrResult.cast()); | ||
final result = BSTR._(pbstrResult.value); | ||
calloc.free(pbstrResult); | ||
return result; | ||
} | ||
|
||
/// Allocates a new string that is a copy of the existing string. | ||
BSTR clone() => BSTR._(SysAllocString(ptr)); | ||
|
||
/// Returns the contents of the BSTR as a regular Dart string. | ||
@override | ||
String toString() => ptr.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
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
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,86 @@ | ||
@TestOn('windows') | ||
|
||
import 'dart:ffi'; | ||
|
||
import 'package:ffi/ffi.dart'; | ||
import 'package:test/test.dart'; | ||
import 'package:win32/win32.dart'; | ||
|
||
const TEST_RUNS = 500; | ||
|
||
void main() { | ||
test('BSTR allocation', () { | ||
const testString = 'Hello world'; | ||
final bstr = BSTR.fromString(testString); | ||
|
||
// A BSTR should have a DWORD-length prefix containing its length. | ||
final pIndex = | ||
Pointer<DWORD>.fromAddress(bstr.ptr.address - sizeOf<DWORD>()); | ||
expect(pIndex.value, equals(testString.length * 2)); | ||
|
||
expect(bstr.ptr.toDartString(), equals(testString)); | ||
|
||
// A BSTR should end with a word-length null terminator. | ||
final pNull = | ||
Pointer<DWORD>.fromAddress(bstr.ptr.address + testString.length * 2); | ||
expect(pNull.value, isZero); | ||
bstr.free(); | ||
}); | ||
|
||
test('Long BSTRs', () { | ||
// This string is 4MB (32 chars * 2 bytes * 65536) | ||
final longString = 'A very long string with padding.' * 65536; | ||
final bstr = BSTR.fromString(longString); | ||
|
||
// A BSTR should have a DWORD-length prefix containing its length. | ||
final pIndex = | ||
Pointer<DWORD>.fromAddress(bstr.ptr.address - sizeOf<DWORD>()); | ||
expect(pIndex.value, equals(longString.length * 2)); | ||
|
||
expect(bstr.ptr.toDartString(), equals(longString)); | ||
|
||
// A BSTR should end with a word-length null terminator. | ||
final pNull = | ||
Pointer<DWORD>.fromAddress(bstr.ptr.address + longString.length * 2); | ||
expect(pNull.value, isZero); | ||
bstr.free(); | ||
}); | ||
|
||
test('BSTR lengths', () { | ||
const testString = 'Longhorn is a bar in the village resort between the ' | ||
'Whistler and Blackcomb mountains'; | ||
final bstr = BSTR.fromString(testString); | ||
|
||
expect(testString.length, equals(84)); | ||
expect(bstr.byteLength, equals(84 * 2)); | ||
expect(bstr.length, equals(84)); | ||
|
||
expect(bstr.toString(), equals(testString)); | ||
|
||
bstr.free(); | ||
}); | ||
|
||
test('BSTR clone', () { | ||
const testString = 'This message is not unique.'; | ||
final original = BSTR.fromString(testString); | ||
final clone = original.clone(); | ||
|
||
// Text should be equal, but pointer address should not be equal | ||
expect(original.ptr.toDartString(), equals(clone.ptr.toDartString())); | ||
expect(original.toString(), equals(clone.toString())); | ||
expect(original.ptr, isNot(equals(clone.ptr))); | ||
|
||
clone.free(); | ||
original.free(); | ||
}); | ||
|
||
test('BSTR concatenation', () { | ||
final first = BSTR.fromString('Windows'); | ||
final second = BSTR.fromString(' and Dart'); | ||
final matchInHeaven = first + second; | ||
|
||
expect(matchInHeaven.toString(), equals('Windows and Dart')); | ||
|
||
[first, second, matchInHeaven].map((object) => object.free()); | ||
}); | ||
} |
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