[X]
or Alt+F4
), the application does not terminate completely—it remains in the Task Manager's process list.WndProc
function and simply return DefWindowProc()
, the issue persists. The program lingers in the background, and I have to manually kill it from the Task Manager.#include <Windows.h>
#include <stdio.h>
#include <gl/GL.h>
#include <iostream>
const char *GREEN = "\x1b[38;2;45;200;120m";
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void GeneratePixelFormat(HDC &dc);
void SettingUpConsole();
void SetupRC(HDC hDC, GLuint nFontList);
void RenderScene(GLuint nFontList);
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdArg, int nCmd)
{
// SettingUpConsole();
WNDCLASS wc = {0};
wc.hInstance = hInstance;
wc.lpszClassName = "Window Class";
// ? CS_OWNDC is required in OpenGL because some old drivers are not very stable without this (check page 656 to 660 in OpenGL SuperBbile)
wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wc.lpfnWndProc = WndProc;
wc.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(248, 248, 255));
wc.cbWndExtra = 0;
wc.cbClsExtra = 0;
if (!RegisterClass(&wc))
return 0;
HWND WindowHandle = CreateWindowExA(
0, wc.lpszClassName, "Custom Window",
// ! When making window for OpenGL it is required to set clipchildren and siblings
// ? If not used then opengl may draw onto sibling or children window also
WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 0, 0, 1000, 460,
NULL, NULL, 0, NULL);
if (!WindowHandle)
{
return 0;
}
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
// printf("Running message loop.\n");
}
printf("\x1b[38;2;120mExiting Loop.");
return 0;
}
LRESULT CALLBACK WndProc(HWND WindowHandle, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static HDC dc = NULL;
static HGLRC rc = NULL;
static GLuint nFontList;
switch (uMsg)
{
case WM_CREATE:
{
printf("%sGetting window's handle\n", GREEN);
dc = GetDC(WindowHandle);
printf("%sSetting pixel format\n", GREEN);
GeneratePixelFormat(dc);
printf("%sCreating rendering context...\n", GREEN);
rc = wglCreateContext(dc);
printf("%sSetting current context.\n", GREEN);
wglMakeCurrent(dc, rc);
printf("%sSending message 30 times per second (30fps)\n", GREEN);
SetTimer(WindowHandle, 101, 33, NULL);
SetupRC(dc, nFontList);
printf("\x1b[38;2;150;150;150m\n");
return 0;
}
case WM_TIMER:
{
InvalidateRect(WindowHandle, NULL, FALSE);
printf("MESSAGE SENT\n");
return 0;
}
case WM_CLOSE:
{
printf("WM_CLOSE received. Destroying window...\n");
ReleaseDC(WindowHandle, dc);
wglMakeCurrent(dc, NULL);
wglDeleteContext(rc);
DestroyWindow(WindowHandle);
return 0;
}
case WM_DESTROY:
{
printf("WM_DESTROY received. Posting quit message...\n");
PostQuitMessage(0);
KillTimer(WindowHandle, 101);
// ! Do not remove ExitProcess T_T U_U
// ? Don't know why but closing the window don't close the process in the task manager
// ? Therefore using ExitProcess(0) to force process to stop when wm_destory message received.
// ExitProcess(0);
return 0;
}
case WM_SIZE:
{
RenderScene(nFontList);
SwapBuffers(dc);
ValidateRect(WindowHandle, NULL);
return 0;
}
case WM_PAINT:
{
RenderScene(nFontList);
SwapBuffers(dc); // Ensure the rendered scene is displayed
ValidateRect(WindowHandle, NULL);
return 0;
}
default:
return DefWindowProc(WindowHandle, uMsg, wParam, lParam);
}
}
void SettingUpConsole()
{
AllocConsole();
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
// Set console to ASCII mode
SetConsoleOutputCP(CP_ACP);
// Enable virtual terminal processing
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD consoleMode;
GetConsoleMode(hConsole, &consoleMode);
consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(hConsole, consoleMode);
}
void GeneratePixelFormat(HDC &dc)
{
static PIXELFORMATDESCRIPTOR pfd = {
sizeof(PIXELFORMATDESCRIPTOR), // Size of this structure
1, // Version of this structure
PFD_DRAW_TO_WINDOW | // Draw to window (not to bitmap)
PFD_SUPPORT_OPENGL | // Support OpenGL calls in window
PFD_DOUBLEBUFFER, // Double buffered mode
PFD_TYPE_RGBA, // RGBA color mode
32, // Want 32-bit color
0, 0, 0, 0, 0, 0, // Not used to select mode
0, 0, // Not used to select mode
0, 0, 0, 0, 0, // Not used to select mode
16, // Size of depth buffer
0, // No stencil
0, // No auxiliary buffers
0, // Obsolete or reserved
0, // No overlay and underlay planes
0, // Obsolete or reserved layer mask
0, // No transparent color for underlay plane
0}; // Obsolete
int nPixelFormat = ChoosePixelFormat(dc, &pfd);
SetPixelFormat(dc, nPixelFormat, &pfd);
}
void SetupRC(HDC hDC, GLuint nFontList)
{
// Set up the font characteristics
HFONT hFont;
GLYPHMETRICSFLOAT agmf[128]; // Throw away
LOGFONT logfont;
logfont.lfHeight = -10;
logfont.lfWidth = 0;
logfont.lfEscapement = 0;
logfont.lfOrientation = 0;
logfont.lfWeight = FW_BOLD;
logfont.lfItalic = FALSE;
logfont.lfUnderline = FALSE;
logfont.lfStrikeOut = FALSE;
logfont.lfCharSet = ANSI_CHARSET;
logfont.lfOutPrecision = OUT_DEFAULT_PRECIS;
logfont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
logfont.lfQuality = DEFAULT_QUALITY;
logfont.lfPitchAndFamily = DEFAULT_PITCH;
strcpy(logfont.lfFaceName, "Arial");
// Create the font and display list
hFont = CreateFontIndirect(&logfont);
SelectObject(hDC, hFont);
// Create display lists for glyphs 0 through 128 with 0.1 extrusion
// and default deviation. The display list numbering starts at 1000
// (it could be any number).
nFontList = glGenLists(128);
wglUseFontOutlines(hDC, 0, 128, nFontList, 0.0f, 0.5f,
WGL_FONT_POLYGONS, agmf);
DeleteObject(hFont);
}
void RenderScene(GLuint nFontList)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Blue 3D text
glColor3ub(0, 0, 255);
glPushMatrix();
glListBase(nFontList);
glCallLists(6, GL_UNSIGNED_BYTE, "SDFSDF");
glPopMatrix();
}
# My Folders
include_folder = ./include
lib_folder = ./lib
# Libraries to include
my_libraries = User32.lib Gdi32.lib ucrt.lib kernel32.lib vcruntime.lib msvcrt.lib opengl32.lib
# INFO(Include Folders)
# Windows Kit Include Folders
kit_include_1 = "C:/Program Files (x86)/Windows Kits/10/Include/10.0.22621.0/winrt"
kit_include_2 = "C:/Program Files (x86)/Windows Kits/10/Include/10.0.22621.0/ucrt"
kit_include_3 = "C:/Program Files (x86)/Windows Kits/10/Include/10.0.22621.0/um"
kit_include_4 = "C:/Program Files (x86)/Windows Kits/10/Include/10.0.22621.0/shared"
kit_include_5 = "C:/Program Files (x86)/Windows Kits/10/Include/10.0.22621.0/cppwinrt"
# Windows Kit Lib Folders
kit_lib_1 = "C:/Program Files (x86)/Windows Kits/10/Lib/10.0.22621.0/um/x64"
kit_lib_2 = "C:/Program Files (x86)/Windows Kits/10/Lib/10.0.22621.0/ucrt/x64"
# MSVC Include Folders
msvc_include = "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.41.34120/include"
msvc_atlmfc_include = "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.41.34120/atlmfc/include"
# MSVC Lib Folders
msvc_lib = "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.41.34120/lib/x64"
msvc_atlmfc_lib = "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Tools/MSVC/14.41.34120/atlmfc/lib/x64"
# INFO(Combined Include Folders)
include_folders = /I$(msvc_include) /I$(msvc_atlmfc_include) /I$(kit_include_1) /I$(kit_include_2) /I$(kit_include_3) /I$(kit_include_4) /I$(kit_include_5) /I$(include_folder) /I"C:/Program Files (x86)/Windows Kits/10/Include/10.0.22621.0/shared"
#INFO(Combined Lib Folders)
lib_folders = /LIBPATH:$(msvc_lib) /LIBPATH:$(msvc_atlmfc_lib) /LIBPATH:$(kit_lib_1) /LIBPATH:$(kit_lib_2) /LIBPATH:$(lib_folder) $(my_libraries)
# When using windows kit 10.0.19041.0 i.e. Win32
build:
cl main.cpp ${include_folders} /EHsc /link ${lib_folders} /OUT:./bin/program.exe /SUBSYSTEM:WINDOWS /NODEFAULTLIB /ENTRY:WinMain
del *.obj
# ? Testing if I can use my custom made Logger.dll if I use g++ rather than msvc, because I was not able to use Logger.dll with msvc
# building with g++ without -mwindows flag
# -mwindows flag is used to hide the console window
# mark the entry point as WinMain
buildGPP:
g++ main.cpp -o ./bin/program.exe -I$(include_folder) -L$(lib_folder) -L./bin -lUser32 -lGdi32 -lucrt -lkernel32 -lLogger
DefWindowProc()
in WndProc, but the process still remains.WM_CLOSE
calls DestroyWindow()
, and WM_DESTROY
calls PostQuitMessage(0)
.printf("Exiting main loop."))
, which does print, meaning the loop terminates but the process does not.WinMain
the program is not able to close properly, but not specifying the flag does it. So is the problem related to compiler, I am using MSVC 2022 version. Answer from @tommybee and hints from @Mippy, @Red.Wave, @Mantee.Pink suggested to use console as starting point, and that also does help, but then what is problem with WinMain?The issue isn't in the code. It is a valid C++ Windows program that terminates when control leaves the WinMain
function. The problem is that the linker command line opts out of C++ language rules by supplying a custom entry point.
The problem can be reproduced with the following program:
#include <Windows.h>
DWORD WINAPI ThreadProc(LPVOID)
{
::Sleep(INFINITE);
return 0;
}
int APIENTRY WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
::CreateThread(nullptr, 0, ThreadProc, nullptr, 0, nullptr);
return 0;
}
The primary thread creates a thread that sleeps indefinitely and returns immediately. Per C and C++ language rules returning from the entry point has the effect of terminating the program.
This happens when compiling the program using the following command line:
cl main.cpp /EHsc /link kernel32.lib /OUT:./bin/program.exe /SUBSYSTEM:WINDOWS
If we instead set a custom entry point via the /ENTRY
linker option, the program will not terminate when the primary thread returns from WinMain
:
cl main.cpp /EHsc /link kernel32.lib /OUT:./bin/program.exe /SUBSYSTEM:WINDOWS /NODEFAULTLIB /ENTRY:WinMain
Launching program.exe now keeps the process alive indefinitely. If you attach a debugger, you will see that the process has a single thread that is sleeping (ThreadProc
).
When setting a custom entry point rather than letting the linker choose the default WinMainCRTStartup
the rules for process termination change: A process terminates when a thread calls ExitProcess
explicitly or when all threads have terminated. Raymond Chen explains this in his blog entry If you return from the main thread, does the process exit? in more detail.
While calling ExitProcess
from the custom WinMain
entry point would address the immediate issue, it is not a practical solution. The language support libraries don't just terminate a process for us. They are responsible for a lot of things (such as initializing objects with static storage duration). The real solution is to drop the /ENTRY
linker option and let the linker choose the default entry point.