Strange behavior of TextOut() Win32 API C++

I'm making a very simple game using C++ and Win32 API. I'm still new to the API. When I'm constructing my title screen, I noticed a very strange behavior of `TextOut()`. Here's my code:

1
2
3
4
5
6
7
8
9
//titleFont is supposed to be a global variable
HFONT titleFont = CreateFont(100, 40, 0, 0, FW_MEDIUM, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_CHARACTER_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE, _T("Arial"));
...
//hwndRect is a the client RECT of my main window
//hdc is a global variable storing the DC of hwnd
//The following code is in a callback function's switch statement (not WndProc())
FillRect(hdc, &hwndRect, (HBRUSH)(COLOR_WINDOW + 1));
SelectObject(hdc, titleFont);
TextOut(hdc, hwndRect.right / 2 - 300, hwndRect.bottom / 2 - 100, _T("teststr"), sizeof(_T("teststr")) / sizeof(TCHAR));


The text is successfully drawn. However, `TextOut()` has also drawn the null terminator (it appeared as a block). The cause of this problem was obvious to me, but if I change the line to this:
 
TextOut(hdc, hwndRect.right / 2 - 300, hwndRect.bottom / 2 - 100, _T("teststr"), (sizeof(_T("teststr")) / sizeof(TCHAR)) - 1);

Nothing shows up at all! Even if I do this:
 
TextOut(hdc, hwndRect.right / 2 - 300, hwndRect.bottom / 2 - 100, _T("teststr"), _tclen(_T("teststr")));

Nothing shows up. I'm very confused, since in the MSDN docs, there is this line:
"...
lpString [in]
A pointer to the string to be drawn. The string does not need to be zero-terminated, because cchString specifies the length of the string.
cchString [in]
The length of the string pointed to by lpString, in characters."
Which states that the last argument should be the length of the string WITHOUT the null terminator.
How can I get text to show up and without the box for the null terminator?
I think it's a problem with precedence
This should work:
TextOut (hDC, 100, 100, _T ("teststr"), (sizeof (_T ("teststr")) / sizeof (TCHAR)) - 1);

Another way:
1
2
TCHAR szText[] = _T ("Hello World");
TextOut (hDC, 100, 200, szText, ARRAYSIZE (szText));
@Thomas1965 thanks but none of them works.
For the first one nothing shows up, the second the string shows up with the null terminator. If I change the last parameter to ARRAYSIZE(szText) - 1 then nothing shows up at all.

Not even this works:
TextOut(hdc, hwndRect.right / 2 - 300, hwndRect.bottom / 2 - 100, _T("teststr"), 7);

Here's my entire code, if you want to see it:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#include <windows.h>
#include <cstdlib>
#include <cstring>
#include <string>
#include <tchar.h>

LPCTSTR wndClassName = _T("win32app");

LPCTSTR wndTitle = _T("Flappy Sqare");

HFONT titleFont = CreateFont(100, 40, 0, 0, FW_MEDIUM, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_CHARACTER_PRECIS, ANTIALIASED_QUALITY, FF_DONTCARE, _T("Arial"));


HINSTANCE hInst;
HWND hwnd;

HDC hdc;

RECT hwndRect;

int scene = 1;
POINT sqarePos;
BOOL keyIsDown = FALSE;
BOOL sqareIsFlyingUp = FALSE;
int distanceFlied = 0;
const int maxDistanceFlied = 50;
const int flySpeed = 4;
const int fallSpeed = 3;

VOID CALLBACK WaitOrTimerCallback(PVOID lpParam, BOOL TimerOrWaitFired) {
	switch (scene) {
	case 1:
	{
		FillRect(hdc, &hwndRect, (HBRUSH)(COLOR_WINDOW + 1));
		SelectObject(hdc, titleFont);
		TCHAR title[] = _T("Flappy Sqare");
		TextOut(hdc, hwndRect.right / 2 - 300, hwndRect.bottom / 2 - 100, _T("teststr"), 7);
		break;
	}
	case 2:
	{
		FillRect(hdc, &hwndRect, (HBRUSH)(COLOR_WINDOW + 1));
		Rectangle(hdc, sqarePos.x, sqarePos.y, sqarePos.x + 5, sqarePos.y + 5);
		if (sqareIsFlyingUp == FALSE) {
			sqarePos.y += fallSpeed;
		}
		else {
			sqarePos.y -= flySpeed;
			distanceFlied += flySpeed;
			if (distanceFlied >= maxDistanceFlied) {
				sqareIsFlyingUp = false;
			}
		}
		if (sqarePos.y < 0) {
			sqarePos.y = 0;
		}
		break;
	}
	default:break;
	}
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
	switch (msg) {
	case WM_KEYDOWN:
		keyIsDown = TRUE;
		break;
	case WM_KEYUP:
		if (keyIsDown == TRUE) {
			keyIsDown = FALSE;
			switch (scene) {
			case 2:
				sqareIsFlyingUp = TRUE;
				distanceFlied = 0;
				break;
			default:break;
			}
			
		}
		break;
	case WM_CREATE:
	{
		HANDLE timer;
		HANDLE timerQueue = CreateTimerQueue();
		hdc = GetDC(hwnd);
		CreateTimerQueueTimer(&timer, timerQueue, (WAITORTIMERCALLBACK)WaitOrTimerCallback, NULL, 0, 1000 / 30, NULL);
		GetClientRect(hwnd, &hwndRect);
		sqarePos.x = hwndRect.right / 2 - 100;
		sqarePos.y = hwndRect.bottom / 2;
		break;
	}
	case WM_SIZE:
		GetClientRect(hwnd, &hwndRect);
		sqarePos.y = hwndRect.bottom / 2;
		break;
	case WM_PAINT:
	{
		PAINTSTRUCT ps;
		HDC hdc = BeginPaint(hwnd, &ps);
		FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
		EndPaint(hwnd, &ps);
		break;
	}
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hwnd, msg, wParam, lParam);
	}

	return 0;
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
	hInst = hInstance;
	WNDCLASSEX wcex;
	wcex.cbSize = sizeof(WNDCLASSEX);
	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = WndProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = NULL;
	wcex.lpszClassName = wndClassName;
	wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

	if (!RegisterClassEx(&wcex)) {
		MessageBox(NULL, _T("Call to RegisterClassEx failed!"), _T("ERROR"), MB_ICONERROR);

		return 1;
	}
	//CreateWindowEx's first parameter is one of the extended window styles (WS_EX_*), 
	//eighth parameter is the parent of the window, ninth is the menu
	hwnd = CreateWindowEx(NULL, wndClassName, wndTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 700, 500, NULL, NULL, hInstance, NULL);

	if (!hwnd) {
		MessageBox(NULL, _T("Call to CreateWindow failed!"), _T("ERROR"), MB_ICONERROR);

		return 1;
	}

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);

	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return (int)msg.wParam;
}
I've solved the problem, but I can't figure out why my code didn't work before or why it worked now. In fact, the solution to this problem is so weird it makes absolutely no sense to me.

Here's the code that works:

1
2
TextOut(hdc, hwndRect.right / 2 - 300, hwndRect.bottom / 2 - 100, _T("teststr"), ARRAYSIZE(_T("teststr")) - 1);
TextOut(hdc, hwndRect.right / 2 - 300, hwndRect.bottom / 2 - 100, _T("teststr2"), ARRAYSIZE(_T("teststr2")) - 1);


I have to call the function TWICE to solve this problem.
What's even more weird is that, the first string displays all right, but absolutely nothing shows up in the second call to TextOut(). I know that by passing in a different string for each of the calls. It works if the args passed in are the same in each of the calls, or if the args are different, as long as the HDCs passed in are the same (not even the coordinates matter), and that the string passed in to the last call to TextOut() has the same or longer length than all of the calls to the ones above.

And, as long as I have called the function one last time, all of my previous calls work. Example: this
1
2
3
TextOut(hdc, hwndRect.right / 2 - 300, hwndRect.bottom / 2 - 100, _T("teststr"), ARRAYSIZE(_T("teststr")) - 1);
TextOut(hdc, hwndRect.right / 2 - 300, hwndRect.bottom / 2 + 100, _T("teststr1"), ARRAYSIZE(_T("teststr1")) - 1);
TextOut(hdc, hwndRect.right / 2 - 300, hwndRect.bottom / 2 - 100, _T("teststr2"), ARRAYSIZE(_T("teststr2")) - 1);

actually draws both "teststr" and "teststr1".

Anyone know why this is happening?
Last edited on
Why not just explicitly use Unicode instead of all this TCHAR nonsense?

Then, instead of ARRAYSIZE, perhaps using the STL would be a simpler approach?

Then this:
 
TextOut(hdc, hwndRect.right / 2 - 300, hwndRect.bottom / 2 - 100, _T("teststr"), ARRAYSIZE(_T("teststr")) - 1);


could become this:

1
2
std::wstring wstrTest = L"teststr";
TextOut(hdc, hwndRect.right / 2 - 300, hwndRect.bottom / 2 - 100, wstrTest.c_str(), wstrTest.size());


I can't say for sure if this fixes your problem or not since on my machines what you describe does not happen. Maybe worth a try though.
@Hydranix,

the idea of TCHAR is that you can use use the code either in Unicode or with Multi Byte Character set - particular useful when dealing with legacy code.
Topic archived. No new replies allowed.