Strings
Win32 APIs make heavy use of strings for parameters and return values.
Unlike normal Dart strings, native strings live in native memory and follow explicit ownership rules.
Win32 uses multiple string formats:
- UTF-16 (
WCHAR*,LPWSTR,PCWSTR,PWSTR) — modern Win32 APIs - ANSI (
CHAR*,LPSTR,PCSTR,PSTR) — legacy APIs (code-page–dependent; often UTF-8 in practice, but not guaranteed) - BSTR — COM automation
- HSTRING — WinRT
Modern Win32 APIs almost always use UTF-16 (PCWSTR or PWSTR).
Dart Strings to Native Strings​
package:win32 provides APIs for converting Dart strings into
native representations.
Choose the representation based on the API signature, then choose the allocation style based on lifetime requirements.
All of these methods allocate native memory and return lightweight wrapper types around the underlying pointer or handle.
Allocation strategies:
- Arena-based allocation ties the string's lifetime to a lexical scope and releases it automatically when the arena is disposed.
- Manual allocation returns a string whose lifetime you must manage explicitly using the appropriate deallocation API.
If you use the StringExtension methods, you are responsible
for freeing the allocated memory:
PCWSTR/PWSTR/PCSTR/PSTR->free()BSTR->SysFreeString()HSTRING->WindowsDeleteString()
Prefer Scope-based Lifetime Management with Arena to allocate native
strings unless you have a compelling reason to manage their lifetime manually.
UTF-16 (PCWSTR / PWSTR)​
using((arena) {
final text = arena.pcwstr('MessageBox Demo');
MessageBox(null, text, null, MB_OK);
});
This allocates a null-terminated UTF-16 string and returns a PCWSTR
wrapper around Pointer<Utf16>.
ANSI (PCSTR / PSTR)​
using((arena) {
final name = arena.pcstr('kernel32.dll');
// Use with APIs that require PCSTR
});
This allocates a null-terminated string of 8-bit Windows (ANSI) characters and
returns a PCSTR wrapper around Pointer<Utf8>.
COM Strings (BSTR)​
using((arena) {
final name = arena.bstr('Hello');
});
This allocates a COM BSTR string and returns a BSTR wrapper around
Pointer<Utf16>.
WinRT Strings (HSTRING)​
using((arena) {
final name = arena.hstring('Hello');
});
This creates an immutable, reference-counted HSTRING and returns a
HSTRING wrapper around Pointer.
To convert a HSTRING back to a Dart string, use the
HSTRING.toDartString() method.
Native Strings to Dart Strings​
Many Win32 APIs write UTF-16 text into a caller-provided buffer.
When using an Arena, allocate a UTF-16 buffer with
ArenaExtension.pwstrBuffer().
The following example calls the SHGetFolderPath() API to
retrieve the path of the Desktop folder:
String getDesktopFolderPath() {
return using((arena) {
final path = arena.pwstrBuffer(MAX_PATH);
SHGetFolderPath(CSIDL_DESKTOP, null, 0, path);
return path.toDartString();
});
}
Alternatively, you can allocate the buffer using wsalloc():
String getDesktopFolderPath() {
final path = wsalloc(MAX_PATH);
try {
SHGetFolderPath(CSIDL_DESKTOP, null, 0, path);
return path.toDartString();
} finally {
free(path);
}
}
Unlike the previous example, here you must manually free the allocated memory
using free().
The buffer is converted back to a Dart string using the
Utf16Pointer.toDartString() extension method.
Why doesn't calling .toString() on a Pointer<Utf16> work as
expected?
Since path is a Pointer<Utf16>, calling .toString() on it will simply
print the address of the pointer, like this:
Pointer: address=0x1729cc18240
To convert the Pointer<Utf16> to a Dart string, you need to use the
.toDartString() extension method, as shown in the example above.
Variable-Length Results​
Some APIs require a two-pass pattern:
- Call with a null or zero-length buffer to obtain the required size
- Allocate a buffer of the reported size
- Call again to retrieve the data
String getPathEnvironmentVariable() {
return using((arena) {
final name = arena.pcwstr('PATH');
var Win32Result(value: length, :error) = GetEnvironmentVariable(
name,
null,
0,
);
if (length == 0) throw WindowsException(error.toHRESULT());
final buffer = arena.pwstrBuffer(length);
Win32Result(value: length, :error) = GetEnvironmentVariable(
name,
buffer,
length,
);
if (length == 0) throw WindowsException(error.toHRESULT());
return buffer.toDartString();
});
}
This pattern is common for APIs that return variable-length strings.
MULTI_SZ Strings​
Some Win32 APIs expect a double-NUL-terminated UTF-16 string block (MULTI_SZ).
Use StringListExtension.toPcwstr() or
StringListExtension.toPwstr() to convert a Dart
List<String>:
const values = ['banana', 'strawberry', 'kiwi'];
using((arena) {
final multiSz = values.toPcwstr(allocator: arena);
// Pass to registry or shell APIs
});
The resulting memory layout:
- Each string terminated by a NUL
- A final extra NUL marking the end of the list