0

Given a WinAPI function which returns it's result via a C style string OUT parameter e.g.:

int WINAPI GetWindowTextW(
   _In_   HWND hWnd,
   _Out_  LPTSTR lpString,
   _In_   int nMaxCount
);

Is there a better way of using the function than what I'm doing below?

HWND handle; // Assume this is initialised to contain a real window handle
std::wstring title;
wchar_t buffer[512];
GetWindowTextW(handle, buffer, sizeof(buffer));
title = buffer;

The above code works, but I have the following issues with it:

  1. The buffer size is completely arbitrary since I have no way to know the length of the string that the function might return. This "feels" wrong to me - I have always tried to avoid magic numbers in my code.

  2. If the function returns a string which is larger than the buffer, it will get truncated - this is bad!

  3. Whenever the function returns a string which is smaller than the buffer, I will be wasting memory. This is not as bad as (2), but I'm not thrilled about the idea of setting aside large chunks of memory (e.g. 1024 bytes in my example above) for something that might only need a few bytes in practice.

Are there any other alternatives?

8
  • 1
    Oh my god.. are you the real Jon Bentley? Commented Feb 5, 2013 at 19:57
  • 3
    You should be using _countof, not sizeof - the parameter required is maximum character count, not byte count. Otherwise no, there's not really a better way than how you're using it - although "wasting" a few hundred bytes of stack is not really a major deal, and you can always wrap that code in { } if you want to reclaim the stack immediately. Commented Feb 5, 2013 at 20:01
  • You can use GetWindowTextLength() to determine the length of the string and allocate an appropriately sized buffer. Commented Feb 5, 2013 at 20:04
  • @Pete Thanks, if you post that as an answer I'll upvote it, since it's a good one for the specific example I gave, if not the general problem. Commented Feb 5, 2013 at 20:10
  • why not std::wstring title(buffer)? Commented Feb 5, 2013 at 20:10

2 Answers 2

3

Call the function multiple times with temporary buffers of different sizes. Begin with a buffer of, say, 8. Double the buffer size and call it again. Repeat until it returns the same count as the last time. Then you can allocate the exact size buffer and copy what you've got there. There are a number of Win32 functions with similar behavior.

You may use GetWindowTextLength(), but it probably won't help much if there are race conditions (you may end up with a truncated text because of them).

Sign up to request clarification or add additional context in comments.

2 Comments

Interesting hack, thanks. I'll consider this if I can't find an equivalent to GetWindowTextLength() for other WinAPI functions (as per Pete's suggestion in the comments to my question).
Alexey can go ahead and take the checkmark for this... But I would point out that there are usually ways of doing something similar with many of the Windows API functions of this type. Many return the number of bytes or characters in the data and you can pass in a null buffer first to get the actual length and then use that to create your buffer. Very kludgy, I think, but that's the WinAPI for you.
2

There are a few different patterns depending on which Windows API function you're using. With some, you can query first, sometimes by calling another function (e.g., GetWindowTextLengthW) but usually by passing NULL for the buffer. After you query, you allocate the size and call again to get the actual string data.

Even with query-allocate-query, you sometimes need to iterate as there could be a race condition. For example, consider what happens if the window title changes between the GetWindowTextLengthW and the GetWindowTextW calls.

You can also avoid an extra copy by using the string itself rather than a second buffer.

std::wstring GetWindowTitle(HWND hwnd) {
    std::wstring title(16, L'X');
    int cch;
    do {
      title.resize(2 * title.size());
      cch = GetWindowTextW(hwnd, &title[0], title.size());
    } while (cch + 1 == title.size());
    title.resize(cch);
    return title;
}

While awkward, this isn't really a fault of the Windows API design. The API is designed to be a C interface, not C++. Since C is not object-oriented, it's pretty limited when it comes to handling strings. For C++ code, you can wrap this kind of bookkeeping as I've done in this example.

10 Comments

Your GetWindowTitle() example had several bugs. If GetWindowTextLength() returns 0, the code will loop endlessly. If it returns >0, the code will never loop more than 1 iteration because cch can never be >= title.size(). GetWindowText() returns the number of characters copied not including the null terminator, but the second parameter specifies the maximum characters that can be copied including the null terminator, so cch will always be < title.size() and truncate the result, except when both functions return 0. I corrected it.
@RemyLebeau: Thanks. I got interrupted in the middle of preparing the answer and inadvertently submitted before it was ready. When I get a chance, I'll take another stab at it since it's still not perfect.
You were not allocating the initial wstring correctly, not doing error handling on GetWindowText(), and not taking the null terminator into account correctly. The size passed in to GetWindowText() includes room for a null terminator, but the return value does not include the null terminator. size() does not include the null terminator that is at the end of the wstring memory block.
@RemyLebeau: Both of your edits have introduced a buffer overrun. My last version was tested with an empty title, a short title, and a very long title and returned a correctly-sized string in all cases with no buffer overrun.
@Remy Thanks for all the useful comments, from both of you. I'd be grateful if you could clarify what you mean about switching to std::vector, and how this would alleviate the problem you described?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.