0

I have recently downloaded DevC++ and I am now programming a Windows API program in C++, where I have put a TextBox input control and an OK button to show the text input in a MessageBox().

Here's a small snippet:

HWND TextBox;
char txtStore[200];

/*WM_CREATE*/
TextBox = CreateWindow("edit", "",
    WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL,
    0 ,30 ,500 ,20 ,
    hwnd, (HMENU) 0, NULL, NULL
    ); /* Text Input field */

CreateWindow("button", "Go",
    WS_VISIBLE | WS_CHILD,
    500 ,30 ,100 ,20 ,
    hwnd, (HMENU) 2, NULL, NULL
    ); /* Button 8/

/*WM_COMMAND*/

if(LOWORD(wParam)==2){
    GetWindowText(TextBox,&txtStore[0],200);
    MessageBox(0,txtStore,"Title",0);
}

How do I save the input into std::string txtStore; instead of char txtStore[200];?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Subha Jeet Sikdar
  • 446
  • 1
  • 4
  • 15
  • 1
    What is `string`? Do you mean `std::string`? – Thomas Sablik Jul 30 '19 at 15:20
  • 2
    Possible duplicate of [Converting a C-style string to a C++ std::string](https://stackoverflow.com/questions/4764897/converting-a-c-style-string-to-a-c-stdstring) – Federico klez Culloca Jul 30 '19 at 15:21
  • This may help: https://stackoverflow.com/questions/30961231/how-to-get-a-writable-c-buffer-from-stdstring – drescherjm Jul 30 '19 at 16:20
  • 4
    Don't use DevC++ if you want to learn using the Windows API. The Windows API is exposed as a Unicode interface, whereas DevC++' projects default to MBCS character encoding. A character encoding that no one uses today. – IInspectable Jul 30 '19 at 18:59
  • 1
    When programming for Windows, you should use Unicode mode (`GetWindowTextW`) and `std::wstring`. Just `resize()` the string, then use `string::data()` to get the pointer to the string buffer. At the end, `resize()` again to the return value of `GetWindowTextW`. – rustyx Jul 30 '19 at 19:57
  • @IInspectable MBCS is the most popular encoding family today (UTF-8 is MBCS) – M.M Aug 01 '19 at 05:54
  • @m.m: In context of Windows, MBCS means DBCS. An encoding that no one uses today. – IInspectable Aug 01 '19 at 06:31

1 Answers1

2

For implementations guaranteeing that characters are stored continuously (C++11 and up) you can get text directly into std::wstring.

// UNICODE build - prefered
int potential_length = GetWindowTextLengthW( hwnd )
std::wstring text;
// Theoretically, some implementations can keep internal buffer not NUL terminated
// Allocate one character more just to be safe
text.resize( potential_length + 1 );
int final_length = GetWindowTextW( hwnd, &text[0], potential_length + 1 );
text.resize( final_length );

If you are learning Windows API or writing new software, you definitely should prefer UNICODE builds over ANSI builds. This forces you to use wstring instead of string, wchar_t instead of char, but it saves you from many problems with untranslatable characters (not all UNICODE characters can be represented by current code page), internal API conversions (API has to repeatedly convert strings from narrow to wide characters and other way round).

Yo can save yourself from some traps if you define project wide macros UNICODE and _UNICODE and remove MBCS if it is defined somewhere. You can do this probably in options dialog with field named "additional defines" or something like this. If you don't know how to configure additional macros, defining them just before including <windows.h> header can be acceptable.

#define UNICODE
#define _UNICODE
#include <windows.h>

If you insist on using string and char, here it is.

// ANSI build - obsolete, use only if you have to
// You will lose characters untranslatable into current code page
int potential_length = GetWindowTextLengthA( hwnd );
std::string text;
// Theoretically, some implementations can keep internal buffer not NUL terminated
// Allocate one character more just to be safe
text.resize( potential_length + 1 );
int final_length = GetWindowTextA( hwnd, &text[0], potential_length + 1 );
text.resize( final_length );

Older C++ standards disallow modifying internal string data. You could use some intermediate buffer defined on stack, obtained with new wchar_t[] or as I prefer with std::vector.

// UNICODE build - preferred
int potential_length = GetWindowTextLengthW( hwnd );
std::vector<wchar_t> buff;
buff.resize( potential_length + 1 );
int final_length = GetWindowTextW( hwnd, buff.data(), potential_length + 1 );
std::wstring text( buff.data(), final_length );
// ANSI build - obsolete, use only if you have to
// You will lose characters untranslatable into current code page
int potential_length = GetWindowTextLengthA( hwnd );
std::vector<char> buff;
buff.resize( potential_length + 1 );
int final_length = GetWindowTextA( hwnd, buff.data(), potential_length + 1 );
std::string text( buff.data(), final_length );

If compiler doesn't support buff.data(), use &buff[0].


Error handling

On error GetWindowTextLengthW returns 0, so to check error you would need to SetLastError(0) before calling it and check GetLastError() result after call. GetWindowTextW on error returns 0 and checking must be done with same steps. This is because 0 is valid text length.

SetLastError( 0 );
int potential_length = GetWindowTextLengthW( hwnd );
if ( potential_length == 0 )
{
    DWORD error = GetLastError();
    if ( error != 0 )
    {
        // Handle error here. Throw exception, log message, display dialog etc.
        // Most probably hwnd handle is invalid.
        return;
    }
}
std::wstring text;
// Theoretically, some implementations can keep internal buffer not NUL terminated
// Allocate one character more just to be safe
text.resize( potential_length + 1 );
SetLastError( 0 );
int final_length = GetWindowTextW( hwnd, &text[0], potential_length + 1 );
if ( final_length == 0 )
{
    DWORD error = GetLastError();
    if ( error != 0 )
    {
        // Handle error here. Throw exception, log message, display dialog etc.
        // Most probably hwnd handle is invalid or belongs to another process.
        return;
    }
}
text.resize( final_length );

+ 1 and length manipulation explanation

Additional + 1 covers quirks of the Windows API where some API calls take into account trailing NUL characters, and some don't. This is the case with GetWindowTextLengthW which returns length of text without NUL character, but last parameter in GetWindowTextW needs to state buffer size including place for NUL character. If we omit + 1 buffer would contain one character less because API have to put NUL character.

Documentation states also that GetWindowTextLength can return value few character larger (I think it applies mostly to GetWindowTextLengthA variant). This explains length correction, because only after GetWindowTextW we know true length.


Over-allocating string space

Most string implementations which keep string characters continuously also add NUL character at end to simplify c_str() and data() handling, but taking C++11 standard literally, they don't have to. Because GetWindowText always puts NUL character at end, for implementations that do not keep additional space for NUL character permanently it could overwrite some internal data.

Although

text.resize( potential_length );

could work, resizing string one character more

text.resize( potential_length + 1 );

will cover these implementations. This has a cost of always resizing text latter to smaller length and unnecessary allocation if we cross string inline storage size.

Daniel Sęk
  • 2,504
  • 1
  • 8
  • 17
  • This is even slower than OP's solution, since `vector::resize()` zero-fills the buffer. – rustyx Jul 30 '19 at 19:44
  • 1
    @IInspectable When I remove all `+1` I get window title without last character – Daniel Sęk Jul 30 '19 at 20:09
  • 1
    @IInspectable The argument for `nMaxCount` of `GetWindowText[A|W]` has to account for the null terminator. OTOH `GetWindowTextLength[A|W]` return the length of the text, not including the null terminator. So the `+ 1` accounts for the difference. – zett42 Jul 30 '19 at 20:10
  • @rustyx But this solution gets text of any size. What if edit control keeps some path, then you get part of it, and recursively delete directories starting from wrong directory? vector could be bypassed if C++11 string guarantees are supported – Daniel Sęk Jul 30 '19 at 20:15
  • 1
    BTW, I would flip your examples to use `wchar_t` / `std::wstring` by default. I think there are much less (by a margin) people caring for ANSI builds these days. Though beginners tend to copy-paste such examples and later have to relearn when starting to write professional code. – zett42 Jul 30 '19 at 20:16
  • 1
    @zett42 I have decided to add both versions with short passage that OP should use UNICODE version even if he states in question that he wants to get text into `string`. – Daniel Sęk Jul 31 '19 at 05:31
  • I'm sorry, I did read something into the code that wasn't there. Removed those comments. The code writing straight into the `std::string` isn't valid C++11, though. [\[string.access\]/2](http://eel.is/c++draft/string.access#2) was updated in C++17 to allow overwriting the implicit zero-terminator. In C++11 you'd have to overallocate, populate, and shrink those strings. – IInspectable Jul 31 '19 at 07:43
  • @IInspectable A have added overallocation by one character. I don't find it feasible to split solution into more versions. Some libraries implement strings with place for NUL character, but taking C++11 standard literally, they don't have to. I think string is still udenspecified so almost all bigger libraries have some own string class (Qt has QString, wxWidgets has wxString etc) focused more on interablility with other APIs. – Daniel Sęk Jul 31 '19 at 10:43