Skip to main content

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:

TIP

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:

  1. Call with a null or zero-length buffer to obtain the required size
  2. Allocate a buffer of the reported size
  3. 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