2

I'm curious if the following code is correct? I'm running it on a somewhat older version of VS 2008, for a Windows-only C++ project.

My goal is to preallocate memory in std::string to pass it into a WinAPI knowing the required size in characters:

//'hWnd' = window handle
int nLn = GetWindowTextLength(hWnd);

//Text variable to collect text in
std::wstring str;
str.reserve(nLn);

GetWindowText(hWnd, (LPTSTR)str.data(), nLn);

My concern here is that str.data() returns const wchar_t * and GetWindowText() requests LPTSTR, which is not a const buffer. Would a type cast be OK there?

c00000fd
  • 20,994
  • 29
  • 177
  • 400
  • 1
    If you cannot call `GetWindowTextW` (vs. `GetWindowText`), you should use `typedef std::basic_string tstring;` as well. Otherwise you have mismatching character types. Likewise, when using a `std::vector`, it should be `std::vector` as well. Or just drop MBCS support and call the Unicode variants of the Windows API. – IInspectable Dec 16 '15 at 01:38
  • I've asked a very-much-related question http://stackoverflow.com/questions/17601632/how-can-i-preallocate-and-initialize-the-character-sequence-inside-stdbasic-s My conclusion was that it was easier to live with an extraneous copy. – eh9 Dec 16 '15 at 03:43
  • @IInspectable: Good point. I overlooked it. Thank you. – c00000fd Dec 16 '15 at 04:06

2 Answers2

3

I can't speak for VS2008, but most pre-C++11 follow the C++11 rule of &str[0] + i == str.data() + i, so &str[0] works and doesn't require any casts1, which is a much safer route than subverting the type system and the standard. Just be careful because you aren't permitted to overwrite the null terminator after the string, even with another null terminator. If you aren't able to guarantee this for VS2008, then a std::vector<wchar_t> would be the preferred solution.

However, you have another problem. reserve doesn't stop it being undefined behaviour to access str out of bounds. Bounds are based on size(), not capacity(). You need to change reserve to resize.

Even then, you have another problem. GetWindowTextLength doesn't include the null terminator. You must use nLn + 1 in the code following that call.

With the changes made, this will work with any conforming C++11 implementation, including some pre-C++11 implementations, and ignoring error checking:

int nLnWithNul = GetWindowTextLength(hWnd);

std::wstring str(nLnWithNul, '\0'); // bit of a shorthand for the resize call
int nCopiedLn = GetWindowText(hWnd, &str[0], nLnWithNul);
str.resize(nCopiedLn);

The last resize call handles the copied title being shorter than nLnWithNul. For example, if the title shrinks since GetWindowTextLength was called. After resizing, the string will contain only the copied text, not including any NUL characters after it.


1: See the answer to my previous question.

Community
  • 1
  • 1
chris
  • 60,560
  • 13
  • 143
  • 205
  • You didn't say explicitly, but `(LPTSTR)str.data()` would lead to undefined behaviour. The standard says directly that trying to write via `data()` causes UB. `&str[0]` must be used instead. – M.M Dec 16 '15 at 01:42
  • C++ has always required contiguous storage of the controlled sequence. This wasn't explicitly spelled out, but `c_str()` is required to return in *O(1)*. This leaves only one way to store the controlled sequence. Regardless of the implementation details, you are recommending to rely on undefined behavior. That's not at all prudent. – IInspectable Dec 16 '15 at 01:42
  • @IInspectable please provide a reference to the C++03 standard specifying O(1) requirement for `c_str()`. Also, if your claim is correct (i.e. contiguous storage was always required) then chris's is not recommending to rely on UB – M.M Dec 16 '15 at 01:45
  • @IInspectable, I changed that to the actual guarantee of address equality between the two access methods with a bit of a lack of formalities. That actually spells out that this would work in this scenario. – chris Dec 16 '15 at 01:46
  • @M.M: [std::string::c_str()](http://en.cppreference.com/w/cpp/string/basic_string/c_str): *"Complexity: Constant"*. – IInspectable Dec 16 '15 at 01:53
  • +1 but it would probably be a good idea to do `int resultLen = GetWindowText(...)` and then `str.resize(resultLen)`, to avoid `str.size()` reporting all the `\0` chars – Felix Dombek Dec 16 '15 at 01:54
  • 1
    @IInspectable that website is not the C++03 standard . As far as I can see from reading standard drafts , that requirement was added in C++11. – M.M Dec 16 '15 at 01:57
  • @IInspectable, I agree relying on UB is bad. My main hope is that if the OP can't use it, at least someone with a more recent version of VS can. Definitely one of my least theoretical and more practical answers in that regard. – chris Dec 16 '15 at 01:57
  • @FelixDombek, In general, that's something to consider. Here, the string is immediately filled. You might consider calling `resize` after `GetWindowLength` using the return value as an indicator for how long the string actually is, especially since the documentation is explicit about `GetWindowTextLength` possibly returning a size that is too large. – chris Dec 16 '15 at 01:59
  • 1
    @chris you're right, but the text *could* conceivably change between the two calls. – Felix Dombek Dec 16 '15 at 02:01
  • @FelixDombek, My bad, I misread your comment as resizing it after `GetWindowTextLength`, not `GetWindowText`. We're on the same page here and I'll add that. – chris Dec 16 '15 at 02:03
0

No, you cannot do that according to the C++ standard. But you can do it if you use std::vector<wchar_t> instead of std::wstring:

int nLn = GetWindowTextLength(hWnd);
std::vector<TCHAR> str(nLn); // size, not reserve()
int size = GetWindowText(hWnd, str.data(), nLn);
str.resize(size); // will make empty in case of error

See also: writing directly to std::string internal buffers

Community
  • 1
  • 1
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • Care to elaborate? C++11 introduced guarantees for `std::string`, and I fail to see any violations. – IInspectable Jul 30 '19 at 19:03
  • @IInspectable OP specified Visual Studio 2008. I don't think that supports C++11, hence my answer does not consider C++11 (and would be totally different if it did). – John Zwinck Jul 31 '19 at 06:12
  • Pre-C++11, I'm not aware of any rule, that prohibits writing the array at location `&str[0]`. Is there? – IInspectable Aug 01 '19 at 11:29