4

Using WinAPI you can often encounter some methods getting LPWSTR or LPSTR as a parameter. Sometimes this pointer should be a pointer to buffer in fact, for example:

  int GetWindowTextW(HWND hWnd, LPWSTR lpString, int nMaxCount);

Is it a good idea to use std::wstring for such buffers, in particular case I strongly need to produce std::wstring as result and cannot replace it with vector<wchar_t> for example?

std::wstring myWrapper(HWND hWnd){
    auto desiredBufferSize = GetWindowTextLengthW(hWnd);
    std::wstring resultWstr;
    resultWstr.resize(desiredBufferSize);
    auto ret = GetWindowText(hWnd,
                             const_cast<wchar_t*>(resultWstr.data()), // const_cast
                             resultWstr.size());
    // handle return code code
    return resultWstr;
}

Both data() and c_str() string methods return const pointer, so we must use const_cast to remove constness, which sometimes is a bad sign. Is it a good idea in such case? Can I do better?

Christophe
  • 68,716
  • 7
  • 72
  • 138
vard
  • 2,142
  • 4
  • 30
  • 40
  • 4
    Use `&str[0]`, and it's only guaranteed to work in C++11. – chris Sep 07 '14 at 21:57
  • If the code is called by a thread not having sole ownership of the passed `hWnd`, the length might change between the two calls... Would be nice if standard containers had a way to grow to a specified size without setting the new elements... – Deduplicator Sep 07 '14 at 21:59
  • There's very little harm in making a larger-than-necessary buffer, then truncating it to the actual length after fetching the string. – Ben Voigt Sep 07 '14 at 22:00
  • @Deduplicator This is simplified code to specify the question only. This particular WinAPI methods given only for example. – vard Sep 07 '14 at 22:04
  • @vard: Good to hear. Sometimes people are unaware though, and this bug would be subtle... – Deduplicator Sep 07 '14 at 22:06
  • Just to add some food for thought. ;) Sometimes, you could use `unique_ptr` owning array instead of `std::string`. See: [Is there any use for unique_ptr with array?](http://stackoverflow.com/questions/16711697/is-there-any-use-for-unique-ptr-with-array) – Ivan Aksamentov - Drop Sep 07 '14 at 22:21
  • The problem with wstring is that you are dependent on compiling option (unicode/not unicode). – Christophe Sep 07 '14 at 22:33
  • 1
    I think you have an off-by-one error here: `GetWindowTextLength` returns the length (w/o the terminating null character, as far as I can tell). `GetWindowText` takes as its third argument a buffer character count, including the terminating null character. `std::basic_string` (in >= C++11) does not allow modifying the terminating null character, so you *have* to have additional space for that. (Which means that you'll end up with two null characters, so `basic_string` might not be the best tool here.) – dyp Sep 07 '14 at 22:34
  • @chris: no, the contiguous buffer thing was voted into the draft in 2004 or 2005, roughly, the Lillehammer meeting. at that time all extant implementations known to the committee had contiguous buffer, and of course no implementation breaking that has been created since. so it's also guaranteed for C++03. – Cheers and hth. - Alf Sep 07 '14 at 23:02
  • sorry (for now deleted last sentence of previous comment) i thought non-`const` version of `data()` had been introduced in C++11. no such. :( – Cheers and hth. - Alf Sep 07 '14 at 23:05

3 Answers3

3

Use String as C-String

Auto type conversion from const char* to std::string, but not other way around.

The character ‘\0’ is not special for std::string.

  • &s[0] for write access

Make sure the string size (not just capacity) is big enough for C style writing.

  • s.c_str() for read only access

Is valid only until the next call of a non-constant method.

Code sample:

const int MAX_BUFFER_SIZE = 30;         // Including NULL terminator.         
string s(MAX_BUFFER_SIZE, '\0');      // Allocate enough space, NULL terminated
strcpy(&s[0], "This is source string.");    // Write, C++11 only (VS2010 OK)
printf("C str: '%s'\n", s.c_str());     // Read only: Use const whenever possible.
Garland
  • 911
  • 7
  • 22
  • 1
    In the example code you forget to adjust the string length to match the copied C string. Also it's ungood to use all uppercase for non-macro names. Because (1) it's an eyesore and (2) conflicts with the common convention for macro names, risking inadvertent text substitution. – Cheers and hth. - Alf Sep 07 '14 at 23:09
  • `strcpy` into a `std::string` is not really a great idea. `strcpy` itself is not really a great idea these days. – Jonathan Potter Sep 08 '14 at 04:40
  • strcpy(&s[0], "This is source string."); // This line is only a demo of how a legacy API should interface with the std::string. – Garland Sep 08 '14 at 19:04
2

It's tempting to go for nice standard wstring. However it's never good to cast away const...

Here a temporary string wrapper that automatically creates a buffer, passes its pointer to the winapi function, and copies the content of the buffer to your string and disapears cleanly:

auto ret = GetWindowText(hWnd,
                         tmpstr (resultWstr, desiredBufferSize), 
                         resultWstr.size());

This solution works with any windows API function that writes to a character pointer before it returns (i.e. no assync).

How does it work ?

It's based on C++ standard §12.2 point 3 : "Temporary objects are destroyed as the last step in evaluating the full-expression that (lexically) contains the point where they were created. (...) The value computations and side effects of destroying a temporary object are associated only with the full-expression, not with any specific subexpression.".

Here it's implementation:

typedef std::basic_string<TCHAR> tstring;  // based on microsoft's TCHAR

class tmpstr {
private:
    tstring &t;      // for later cpy of the result
    TCHAR *buff;     // temp buffer 
public:
    tmpstr(tstring& v, int ml) : t(v) {     // ctor 
          buff = new TCHAR[ml]{};           // you could also initialize it if needed
           std::cout << "tmp created\n";    // just for tracing, for proof of concept
        }
    tmpstr(tmpstr&c) = delete;              // No copy allowed
    tmpstr& operator= (tmpstr&c) = delete;  // No assignment allowed
    ~tmpstr() {                              
          t = tstring(buff);                // copy to string passed by ref at construction
          delete buff;                      // clean everyhing
          std::cout<< "tmp destroyed";      // just for proof of concept.  remove this line
        }
    operator LPTSTR () {return buff; }  // auto conversion to serve as windows function parameter without having to care
}; 

As you can see, the first line uses a typedef, in order to be compatible with several windows compilation options (e.g. Unicode or not). But of course, you could just replace tstring and TCHAR with wstring and wchar_t if you prefer.

The only drawback is that you have to repeat the buffer size as parameter tmpstr constructor and as parameter of the windows function. But this is why you're writing a wrepper for the function, isn't it ?

Christophe
  • 68,716
  • 7
  • 72
  • 138
  • 1
    I would just write a `GetWindowText` replacement function that handles all this internally and returns a `std::string`. Use `GetWindowTextLength` to work out how big a buffer is needed. – Jonathan Potter Sep 08 '14 at 04:45
  • 1
    @JonathanPotter In fact, that's what the OP wants to achieve. The advantage of the solution I propose here, is that it can work with ANY windows API function that returns strings via LPTSTR (or LPSTR or LPWSTR), under the sole condition that it writes the string before it returns (i.e. not async). – Christophe Sep 08 '14 at 05:45
  • @Cristophe the possible drawback of such approach is that string real copying performs twice - first within winAPI call, and second in wrapper destructor. Isn't it? – vard Sep 08 '14 at 07:49
  • Yes, that's the consequence of respecting the constness of the string data – Christophe Sep 08 '14 at 10:45
  • 1
    Please delete/deactivate the copy ctor and assignment-operator. They won't work as expected. – dyp Sep 08 '14 at 12:02
  • @dyp yes ! good idead ! It's supposed to be used as a temporary, so copy or assignment should not happpen, but you're right: better prevent it. I'll make an edit. – Christophe Sep 08 '14 at 17:42
1

For a string buffer why not to use just char array? :)

DWORD username_len = UNLEN + 1;
vector<TCHAR> username(username_len);
GetUserName(&username[0], &username_len);

the accepted solution is nice example of overthinking.

Igor Zinin
  • 331
  • 3
  • 5
  • Yes, but then how is yours not overthinking compared to Garland's solution, which is the same thing, without obscuring the notion of "strings"? (Granted, his wording was regretfully unfocused and confusing.) – Sz. Aug 02 '19 at 13:18
  • because of that `strcpy(&s[0] ... ` why it is dereferenced first item of the array ? when array name gives you address of its first member? 2) Why not use stdlib? professional code should be clean and nice looking. – Igor Zinin Aug 03 '19 at 14:30
  • GetUserName(username.data(), username.size()); // a cast may be needed from 2nd param's size_t to int – vt. Aug 11 '19 at 14:29