Getting text from Windows C-interface APIs and storing it into STL string objects.
In a previous blog post, we saw that a PWSTR is basically a pointer to a wchar_t array that is typically filled with some Unicode UTF-16 null-terminated text by Windows API calls. In other words, a PWSTR is an output C-style string pointer. You pass it to some Windows API, and, on success, the API will have written some null-terminated text into the caller provided buffer.
Typically, a PWSTR pointer parameter is accompanied by another parameter that represents the size of the output buffer pointed to. In this way, the Windows API knows where to stop when writing the output string, preventing dangerous buffer overflow bugs and security problems.
For example, if you consider the MultiByteToWideChar API prototype1:
int MultiByteToWideChar(
UINT CodePage,
DWORD dwFlags,
LPCCH lpMultiByteStr,
int cbMultiByte,
LPWSTR lpWideCharStr,
int cchWideChar
);
The second to last parameter (lpWideCharStr) is a caller-provided pointer to an output buffer, and the last parameter (cchWideChar) is the size, in wchar_ts, of that output buffer.
Now, suppose that you have an STL string and want to pass it as an output PWSTR parameter. How can you do that?
If you have a std::wstring object, it’s ready to store Unicode UTF-16 text on Windows.
Allocating an External Buffer
To store some UTF-16 text returned by a Windows API via PWSTR parameter in a wstring object, you can allocate a wchar_t buffer of proper size, for example using std::unique_ptr and std::make_unique:
// Allocate an output buffer of proper size
auto buffer = std::make_unique< wchar_t[] >(bufferLength);
Then, you can invoke the desired Windows API, passing the buffer pointer and size:
// Call the Windows API to get some text in the allocated buffer
result = GetSomeTextApi(
buffer.get(), // output buffer pointer (PWSTR)
bufferLength, // size of the output buffer, in wchar_ts
// ...other parameters...
);
// Check 'result' for errors...
Then, since the output buffer is null-terminated, you can use a std::wstring constructor overload to create a std::wstring object from the null-terminated text stored in that buffer:
// Create a wstring that stores the null-terminated text
// returned by the API in the output buffer
std::wstring text(buffer.get());
However, you can do even better than that.
Working Directly with the String’s Internal Buffer
In fact, instead of allocating an external buffer owned by a unique_ptr, and then doing a second string allocation with the std::wstring constructor, you could simply create a wstring of proper size, and then pass to the API the address of the internal string character array. In this way, you work in-place in the wstring character array, without allocating an external buffer. For example:
// Allocate a string of proper size (bufferLength is in wchar_ts)
std::wstring text;
text.resize(bufferLength);
// Get the text from the Windows API
result = GetSomeTextApi(
&text[0], // output buffer pointer (PWSTR)
bufferLength, // size of the output buffer, in wchar_ts
// ...other parameters...
);
Note that the address of the internal string buffer can be obtained with the &text[0] syntax. In addition, since C++17, the std::wstring class offers a convenient wstring::data method, that you can invoke to get the address of the internal string buffer, as well.
Note also that, sine C++17, it’s became legal to overwrite the terminating NUL character in the internal string buffer with another NUL.
On the other hand, with C++11 and C++14, to fully adhere to the C++ standard, you had to allocate a buffer of larger size to make room for the NUL terminator written by the Windows API, and then you had resize down the string to chop off this additional NUL:
text.resize(bufferLength - 1);
I wrote an article for MSDN Magazine on “Using STL Strings at Win32 API Boundaries”. Note that this article predates C++17. So, if your C++ toolkit is compatible with C++17:
- You can invoke the wstring::data method (in addition to the C++11/14 compatible &text[0] syntax)
- You can let Windows APIs overwrite the terminating NUL in the wstring internal buffer with another NUL, instead of making extra room for the additional NUL written by Windows APIs, and then resizing the wstring down to chop it off.2