Skip to content

Commit

Permalink
Close #229
Browse files Browse the repository at this point in the history
  • Loading branch information
Tim Sneath committed May 3, 2022
1 parent 640c6fb commit cc9933f
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 12 deletions.
38 changes: 27 additions & 11 deletions example/idispatch.dart
Expand Up @@ -3,6 +3,7 @@ import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';

/// A helper object to work with IDispatch objects.
class Dispatcher {
final String progID;
final IDispatch disp;
Expand Down Expand Up @@ -76,7 +77,7 @@ class Dispatcher {
}
}

void invokeMethod(int dispid, {Pointer<DISPPARAMS>? params}) {
void invokeMethod(int dispid, [Pointer<DISPPARAMS>? params]) {
Pointer<DISPPARAMS> args;
if (params == null) {
args = calloc<DISPPARAMS>();
Expand Down Expand Up @@ -107,18 +108,33 @@ class Dispatcher {

void main() {
final hr = OleInitialize(nullptr);
if (FAILED(hr)) throw WindowsException(hr);

if (FAILED(hr)) {
print('Failed at OleInitialize.');
throw WindowsException(hr);
}

print('Minimizing all windows via Shell.Application Automation object');
final dispatcher = Dispatcher.fromProgID('Shell.Application');
final dispid = dispatcher.getDispId('MinimizeAll');

dispatcher
..invokeMethod(dispid)
..dispose();
// Example of calling an automation method with no parameters
print('Minimizing all windows via Shell.Application Automation object');
final minimizeAllMethod = dispatcher.getDispId('MinimizeAll');
dispatcher.invokeMethod(minimizeAllMethod);

// Example of calling an automation method with a parameter
print(r'Launching the Windows Explorer, starting at the C:\ directory');
final folderLocation = BSTR.fromString(r'C:\');
final exploreMethod = dispatcher.getDispId('Explore');
final exploreParam = calloc<VARIANT>();
VariantInit(exploreParam);
exploreParam
..ref.vt = VARENUM.VT_BSTR
..ref.bstrVal = folderLocation.ptr;
final exploreParams = calloc<DISPPARAMS>()
..ref.cArgs = 1
..ref.rgvarg = exploreParam;
dispatcher.invokeMethod(exploreMethod, exploreParams);
free(exploreParams);
free(exploreParam);
folderLocation.free();

print('Cleaning up.');
dispatcher.dispose();
OleUninitialize();
}
82 changes: 82 additions & 0 deletions lib/src/bstr.dart
@@ -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();
}
41 changes: 41 additions & 0 deletions lib/src/oleaut32.dart
Expand Up @@ -211,6 +211,47 @@ final _SysStringLen = _oleaut32.lookupFunction<
Uint32 Function(Pointer<Utf16> pbstr),
int Function(Pointer<Utf16> pbstr)>('SysStringLen');

/// Converts a variant from one type to another.
///
/// ```c
/// HRESULT VarBstrCat(
/// [in] BSTR bstrLeft,
/// [in] BSTR bstrRight,
/// [out] LPBSTR pbstrResult
/// );
/// ```
/// {@category oleaut32}
int VarBstrCat(Pointer<Utf16> bstrLeft, Pointer<Utf16> bstrRight,
Pointer<Pointer<Uint16>> pbstrResult) =>
_VarBstrCat(bstrLeft, bstrRight, pbstrResult);

final _VarBstrCat = _oleaut32.lookupFunction<
Int32 Function(Pointer<Utf16> bstrLeft, Pointer<Utf16> bstrRight,
Pointer<Pointer<Uint16>> pbstrResult),
int Function(Pointer<Utf16> bstrLeft, Pointer<Utf16> bstrRight,
Pointer<Pointer<Uint16>> pbstrResult)>('VarBstrCat');

/// Compares two variants of type BSTR.
///
/// ```c
/// HRESULT VarBstrCmp(
/// [in] BSTR bstrLeft,
/// [in] BSTR bstrRight,
/// [in] LCID lcid,
/// [in] ULONG dwFlags
/// );
/// ```
/// {@category oleaut32}
int VarBstrCmp(Pointer<Utf16> bstrLeft, Pointer<Utf16> bstrRight, int lcid,
int dwFlags) =>
_VarBstrCmp(bstrLeft, bstrRight, lcid, dwFlags);

final _VarBstrCmp = _oleaut32.lookupFunction<
Int32 Function(Pointer<Utf16> bstrLeft, Pointer<Utf16> bstrRight,
Uint32 lcid, Uint32 dwFlags),
int Function(Pointer<Utf16> bstrLeft, Pointer<Utf16> bstrRight, int lcid,
int dwFlags)>('VarBstrCmp');

/// Converts a variant from one type to another.
///
/// ```c
Expand Down
6 changes: 5 additions & 1 deletion lib/win32.dart
Expand Up @@ -64,9 +64,14 @@
/// ...
/// free(ansi);
/// ```
///
/// Automation interfaces and some COM methods take a different string type
/// (`BSTR`). The Win32 package supplies a [BSTR] class which wraps the memory
/// allocation functions needed to work with this string data type.
library win32;

// Core Win32 APIs, constants and macros
export 'src/bstr.dart';
export 'src/callbacks.dart';
export 'src/constants.dart';
export 'src/constants_nodoc.dart';
Expand Down Expand Up @@ -139,7 +144,6 @@ export 'src/winrt/winrt_constants.dart';
export 'src/winrt/winrt_helpers.dart';

// COM and Windows Runtime interfaces

export 'src/com/IApplicationActivationManager.dart';
export 'src/com/IApplicationActivationManager.dart';
export 'src/com/IAppxFactory.dart';
Expand Down
18 changes: 18 additions & 0 deletions test/api_test.dart
Expand Up @@ -9934,6 +9934,24 @@ void main() {
int Function(Pointer<Utf16> pbstr)>('SysStringLen');
expect(SysStringLen, isA<Function>());
});
test('Can instantiate VarBstrCat', () {
final oleaut32 = DynamicLibrary.open('oleaut32.dll');
final VarBstrCat = oleaut32.lookupFunction<
Int32 Function(Pointer<Utf16> bstrLeft, Pointer<Utf16> bstrRight,
Pointer<Pointer<Uint16>> pbstrResult),
int Function(Pointer<Utf16> bstrLeft, Pointer<Utf16> bstrRight,
Pointer<Pointer<Uint16>> pbstrResult)>('VarBstrCat');
expect(VarBstrCat, isA<Function>());
});
test('Can instantiate VarBstrCmp', () {
final oleaut32 = DynamicLibrary.open('oleaut32.dll');
final VarBstrCmp = oleaut32.lookupFunction<
Int32 Function(Pointer<Utf16> bstrLeft, Pointer<Utf16> bstrRight,
Uint32 lcid, Uint32 dwFlags),
int Function(Pointer<Utf16> bstrLeft, Pointer<Utf16> bstrRight,
int lcid, int dwFlags)>('VarBstrCmp');
expect(VarBstrCmp, isA<Function>());
});
test('Can instantiate VariantChangeType', () {
final oleaut32 = DynamicLibrary.open('oleaut32.dll');
final VariantChangeType = oleaut32.lookupFunction<
Expand Down
86 changes: 86 additions & 0 deletions test/bstr_test.dart
@@ -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());
});
}
10 changes: 10 additions & 0 deletions tool/inputs/functions.json
Expand Up @@ -6030,6 +6030,16 @@
"dllLibrary": "user32",
"comment": "The ValidateRgn function validates the client area within a region by removing the region from the current update region of the specified window."
},
"VarBstrCat": {
"prototype": "HRESULT VarBstrCat(\n [in] BSTR bstrLeft,\n [in] BSTR bstrRight,\n [out] LPBSTR pbstrResult\n);",
"dllLibrary": "oleaut32",
"comment": "Converts a variant from one type to another."
},
"VarBstrCmp": {
"prototype": "HRESULT VarBstrCmp(\n [in] BSTR bstrLeft,\n [in] BSTR bstrRight,\n [in] LCID lcid,\n [in] ULONG dwFlags\n);",
"dllLibrary": "oleaut32",
"comment": "Compares two variants of type BSTR."
},
"VariantChangeType": {
"prototype": "HRESULT VariantChangeType(\n VARIANTARG *pvargDest,\n const VARIANTARG *pvarSrc,\n USHORT wFlags,\n VARTYPE vt\n);",
"dllLibrary": "oleaut32",
Expand Down

0 comments on commit cc9933f

Please sign in to comment.