Day 16. Close application but process continues in the background. VS2015 update 2

As the title says, When I close the 'Win32Handmade.exe' application window, it disappears, but the process continues in the background, requiring me to force quit each time. Irritating. I don't see this happening on the stream, so I assume it's something particular with my setup - maybe VS2015? Any ideas?
Are you using exact code downloaded from SendOwl? Or have you modified it or wrote it completely from scratch?
Code from SendOwl works fine, it terminates program once window is closed. When window is closed Win32MainWindowCallback receives WM_CLOSE message, then code sets GlobalRunning variable to false. This makes while loop in WinMain function to terminate and exit the function. That will terminate the process.
VS2015 doesn't matter here, it's only question about what code are you running.

Edited by Mārtiņš Možeiko on
Have you edited the code at all? We don't have very much to go on, but the code works fine for me too.
Thanks for the responses.

I'm writing along as I go. Something is evidently incorrect with how I'm processing messages. I can correctly detect close messages and set the global running variable to false, but there are always more messages in the queue to be processed, so the while running loop never has a chance to stop.

 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
Running = true;
    while(Running)
    {       
        MSG Message;
        while(PeekMessage(&Message, 0, 0, 0, PM_REMOVE))
        {           
            switch(Message.message)
            {
                case WM_ACTIVATEAPP:
                {
                } break;

				case WM_QUIT:
				{
					Running = false;
				} break;

                case WM_SYSKEYDOWN:
                case WM_SYSKEYUP:
                case WM_KEYDOWN:
                case WM_KEYUP:
                {
					u32 VKCode = (u32)Message.wParam;
					b32 WasDown = ((Message.lParam & (1 << 30)) != 0);
					b32 IsDown = ((Message.lParam & (1 << 31)) == 0);
					b32 AltKeyWasDown = (Message.lParam & (1 << 29));
					if ((VKCode == VK_F4) && AltKeyWasDown)
					{
						Running = false;
					}

                    if(WasDown != IsDown)
                    {
                        if(VKCode == 'W')
                        {}
                        else if(VKCode == 'S')
                        {}
                        else if(VKCode == 'A')
                        {}
                        else if(VKCode == 'D')
                        {}
                        else if(VKCode == 'Q')
                        {}
                        else if(VKCode == 'E')
                        {}
                        else if(VKCode == VK_ESCAPE)
                        {
                            Running = false;
						}
                    }
				}break;

                default:
                {
                    TranslateMessage(&Message);
                    DispatchMessageA(&Message);
				} break;
            }
        }

Edited by Jesse on Reason: grammah
But are you handling WM_CLOSE message in WindowProc?

WM_QUIT is sent only when PostQuitMessage functionis called. Typically you call it when you receive WM_DESTROY message. So closing window won't generate WM_QUIT message.

Edited by Mārtiņš Možeiko on
I think so? The code you saw above is in WinMain. Below is the callback.

 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
internal LRESULT CALLBACK 
MainWindowCallback(HWND Window, 
                   UINT Message,
                   WPARAM wParam,
                   LPARAM lParam)
{   
    //TODO should handle actual message dispatches?
    
	LRESULT Result = 0;

	switch (Message)
	{
		case WM_QUIT:
		{
			Running = false;
		}break;

		case WM_CLOSE:
		{
			// TODO Handle this with a message to the user?
			Running = false;
		} break;

		case WM_ACTIVATEAPP:
		{
		} break;

		case WM_DESTROY:
		{
			// TODO Handle this as an error - recreate window?
			Running = false;
		} break;

		case WM_SYSKEYDOWN:
		case WM_SYSKEYUP:
		case WM_KEYDOWN:
		case WM_KEYUP:
		{
			Assert(!"Keyboard input came in through a non-dispatch message!");
		} break;

		case WM_PAINT:
		{
		} break;

		default:
		{
			Result = DefWindowProcA(Window, Message, wParam, lParam);
		} break;

	}

	return Result;
}

Edited by Jesse on
Ok, this code is fine. Application should terminate correctly on closing its window.
Unless you are doing strange stuff in some other place of your code that we don't see here :)

Edited by Mārtiņš Možeiko on
Yea, this is kind of driving me slowly insane.

Here's all the code from the win32 layer I have written minus audio / video rendering / file io.

After more debugging, I know I'm never even reaching code beyond the message processing. It's just stuck in there.

  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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#include "jesse_platform.h"

#include <windows.h>
#include <stdio.h>
#include <gl/gl.h>

#include "jesse.h"
#include "jesse.cpp"

internal void
Win32FreeFileMemory(void *Memory)
{
    if(Memory)
    {
        VirtualFree(Memory, 0, MEM_RELEASE);
    }
}

global b32 Running;

internal LRESULT CALLBACK 
MainWindowCallback(HWND Window, 
                   UINT Message,
                   WPARAM wParam,
                   LPARAM lParam)
{   
    //TODO should handle actual message dispatches?
    
	LRESULT Result = 0;

	switch (Message)
	{
		case WM_QUIT:
		{
			Running = false;
		}break;

		case WM_CLOSE:
		{
			// TODO Handle this with a message to the user?
			Running = false;
		} break;

		case WM_ACTIVATEAPP:
		{
		} break;

		case WM_DESTROY:
		{
			// TODO Handle this as an error - recreate window?
			Running = false;
		} break;

		case WM_SYSKEYDOWN:
		case WM_SYSKEYUP:
		case WM_KEYDOWN:
		case WM_KEYUP:
		{
			Assert(!"Keyboard input came in through a non-dispatch message!");
		} break;

		case WM_PAINT:
		{
		} break;

		default:
		{
			Result = DefWindowProcA(Window, Message, wParam, lParam);
		} break;

	}

	return Result;
}

int CALLBACK
WinMain(HINSTANCE hInstance,
            HINSTANCE hPrevInstance, 
            LPSTR lpCmdLine, 
            int nCmdShow)
{
    LARGE_INTEGER PerformanceFrequencyResult;
    QueryPerformanceFrequency(&PerformanceFrequencyResult);
    u64 PerformanceFrequency = PerformanceFrequencyResult.QuadPart;

    WNDCLASSA WindowClass = {};
    WindowClass.style = CS_HREDRAW|CS_VREDRAW|CS_OWNDC;
    WindowClass.hInstance = hInstance;
    //WindowClass.hIcon;
    WindowClass.lpfnWndProc = MainWindowCallback;
    WindowClass.lpszClassName = "JesseClass";

    RegisterClassA(&WindowClass);

    HWND WindowHandle = CreateWindowExA(0,
                                        WindowClass.lpszClassName,
                                        "Jesse",
                                        WS_OVERLAPPEDWINDOW|WS_VISIBLE,
                                        CW_USEDEFAULT,
                                        CW_USEDEFAULT,
                                        CW_USEDEFAULT,
                                        CW_USEDEFAULT,
                                        0,
                                        0,
                                        hInstance,
                                        0);

    Assert(WindowHandle);

    HDC DeviceContext = GetDC(WindowHandle);
    Assert(DeviceContext);

#if INTERNAL
    LPVOID BaseAddress = (LPVOID)Terabytes((u64)2);    
#else
    LPVOID BaseAddress = 0;
#endif

    game_memory GameMemory = {};
    GameMemory.PermStorageSize = Megabytes(64);
    GameMemory.TempStorageSize = Gigabytes((u64)4);
    u64 TotalMemSize = GameMemory.PermStorageSize + GameMemory.TempStorageSize;

    //TODO handle various memory footprints
    GameMemory.PermStorage = VirtualAlloc(BaseAddress, (size_t)TotalMemSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
    GameMemory.TempStorage = ((u8 *)GameMemory.PermStorage + GameMemory.TempStorageSize);

    Assert(GameMemory.PermStorage && GameMemory.TempStorage);

    LARGE_INTEGER LastCounter;
    QueryPerformanceCounter(&LastCounter);
    u64 LastCycleCount = __rdtsc();    

    Running = true;
    while(Running)
    {       
        MSG Message;
        while(PeekMessage(&Message, 0, 0, 0, PM_REMOVE))
        {           
            switch(Message.message)
            {
                case WM_ACTIVATEAPP:
                {
                } break;

		case WM_QUIT:
		{
			Running = false;
		} break;

                case WM_SYSKEYDOWN:
                case WM_SYSKEYUP:
                case WM_KEYDOWN:
                case WM_KEYUP:
                {
		        u32 VKCode = (u32)Message.wParam;
			b32 WasDown = ((Message.lParam & (1 << 30)) != 0);
			b32 IsDown = ((Message.lParam & (1 << 31)) == 0);
			b32 AltKeyWasDown = (Message.lParam & (1 << 29));
			if ((VKCode == VK_F4) && AltKeyWasDown)
			{
				Running = false;
			}

                    if(WasDown != IsDown)
                    {
                        if(VKCode == 'W')
                        {}
                        else if(VKCode == 'S')
                        {}
                        else if(VKCode == 'A')
                        {}
                        else if(VKCode == 'D')
                        {}
                        else if(VKCode == 'Q')
                        {}
                        else if(VKCode == 'E')
                        {}
                        else if(VKCode == VK_ESCAPE)
                        {
                            Running = false;
			}
                    }
		}break;

                default:
                {
                    TranslateMessage(&Message);
                    DispatchMessageA(&Message);
		} break;
            }
        }

        GameUpdateAndRender(&GameMemory);

        LARGE_INTEGER EndCounter;
        QueryPerformanceCounter(&EndCounter);

        u32 MillisecondsElasped = (u32)((1000*(EndCounter.QuadPart - LastCounter.QuadPart))/PerformanceFrequency);
        u64 EndCycleCount = __rdtsc();
        u64 CycleCount = EndCycleCount - LastCycleCount;

        LastCounter = EndCounter;
        LastCycleCount = EndCycleCount;        
    }

    return(0);
}

Edited by Jesse on
Ok, I know what is the problem. WM_PAINT in MainWindowCallback function.

Windows has this concept about update areas when painting in window. If some area is invalid (window minimized and restored, or overlapped from other window), then it calls WM_PAINT and asks window to draw area that is invalid. DefWindowProc doesn't do much in this case and simply says "ok now this area is valid". If you override WM_PAINT then it is your responsibility. If you don't paint and don't validate the update region, then window will still have invalid area. And when MainWindowCallback function will return to Windows internals, then it will see that there is still some area left that is not updated. So it will immediately emit WM_PAINT message again. That means you will receive infinite amount of WM_PAINT messages and while loop with PeekMessage condition will never terminate.

Your fix this by doing one of these things:
1) remove WM_PAINT case from switch, so DefWindowProc is called for WM_PAINT

2) instruct Windows that the are is now valid - you can do that by putting ValidateRect(Window, NULL) inside WM_PAINT case.

3) do the painting in WM_PAINT. Use BeginPaint/EndPaint for this:
1
2
3
4
5
6
case WM_PAINT: {
  PAINTSTRUCT ps;
  HDC dc = BeginPaint(Window, &ps);
  // do the drawing with dc here
  EndPaint(Window, &ps);
}

4) do the actual painting inside main loop, in this case you can do with WM_PAINT whatever you want - use DefWindowProc, use ValidateRect, use BeginPaint/EndPaint. But don't leave it empty!

Option 4 is probably what you want, but you haven't written any code for this yet.

Edited by Mārtiņš Možeiko on
OMG mmozeiko. SRSLY.

I am so extremely impressed with your knowledge and desire to help people. I am also not extremely impressed with Windows. But this is kind of amazing. An empty case statement and just everything blows up. Fabulous.

I owe you a beer.
Hey just wanted to say I'm finally picking up this series again after buying source code over a year ago, and this was driving me NUTS. I am very happy to have run into this thread!

Cheers to both of you. Hopefully, you'll be seeing more of me in these forums!