c++ - Win32: Application process remains in Task Manager after closing the window - Stack Overflow

admin2025-04-17  4

Problem

  • I’ve recently started learning Win32 programming with OpenGL and was experimenting with creating a basic window and testing various functions provided. However, I noticed that when I close the window (either using the close button [X] or Alt+F4), the application does not terminate completely—it remains in the Task Manager's process list.
  • Even when I remove everything from my 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.

Current Code

#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 Makefile

# 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

Question

  1. Am I missing something in the cleanup process or creation process of the window that cause window/process to not terminate in process in task manager?
  2. I am currently manually killing the Process with KillProcess function. Should I keep that or does that create any problem? (Although I have not seen anyone using it.)

What I Tried:

  1. Using only DefWindowProc() in WndProc, but the process still remains.
  2. Ensuring WM_CLOSE calls DestroyWindow(), and WM_DESTROY calls PostQuitMessage(0).
  3. Checked if the message loop exits by printing a log message after it exits (printf("Exiting main loop.")), which does print, meaning the loop terminates but the process does not.

Additional Notes

  1. Are there any debugging steps I should follow to track down why the process persists?

Update 1 - 15 February 2025

  1. It is now clear that the problem is related /ENTRY flag, if I specify entry flag 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?

Problem

  • I’ve recently started learning Win32 programming with OpenGL and was experimenting with creating a basic window and testing various functions provided. However, I noticed that when I close the window (either using the close button [X] or Alt+F4), the application does not terminate completely—it remains in the Task Manager's process list.
  • Even when I remove everything from my 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.

Current Code

#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 Makefile

# 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

Question

  1. Am I missing something in the cleanup process or creation process of the window that cause window/process to not terminate in process in task manager?
  2. I am currently manually killing the Process with KillProcess function. Should I keep that or does that create any problem? (Although I have not seen anyone using it.)

What I Tried:

  1. Using only DefWindowProc() in WndProc, but the process still remains.
  2. Ensuring WM_CLOSE calls DestroyWindow(), and WM_DESTROY calls PostQuitMessage(0).
  3. Checked if the message loop exits by printing a log message after it exits (printf("Exiting main loop.")), which does print, meaning the loop terminates but the process does not.

Additional Notes

  1. Are there any debugging steps I should follow to track down why the process persists?

Update 1 - 15 February 2025

  1. It is now clear that the problem is related /ENTRY flag, if I specify entry flag 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?
Share edited Feb 15 at 13:12 Halleys asked Jan 31 at 12:35 HalleysHalleys 616 bronze badges 36
  • 1 @Mippy Thanks for the timer I intended to use 33 as the third parameter(1000 ms/30 fps) = 33ms, but as for the process I can still see that in the task manager, when window is closed it just becomes a background process and never shuts down if ExitProcess is not used. Task manager screenshots – Halleys Commented Jan 31 at 13:03
  • 5 Break into the debugger and see what threads still exist and what they're doing. Then you can start working backward to figuring out why they are still doing work rather than shutting down. – Raymond Chen Commented Feb 5 at 19:03
  • 1 Run the program without a debugger. Connect the debugger after it hangs. See why the main thread has not exited. – Raymond Chen Commented Feb 8 at 15:29
  • 1 Language services, such as console I/O. – IInspectable Commented Feb 15 at 21:18
  • 2 @IInspectable bingo /ENTRY was the key. If you use it, then you are responsible for running down all threads. If you use the C runtime, it will call ExitProcess for you. devblogs.microsoft.com/oldnewthing/20100827-00/?p=13023 – Raymond Chen Commented Feb 16 at 15:39
 |  Show 31 more comments

3 Answers 3

Reset to default 4

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.

I am trying to think about this problem from a different perspective. If I want the entry point to be 'WinMain' in the linker command, defined as:

cl main.cpp $(include_folders) /EHsc /link $(lib_folders) /OUT:program_$(my_arch).exe /SUBSYSTEM:WINDOWS /NODEFAULTLIB /ENTRY:WinMain

This means I want a custom WinMain function without CRT initialization. So, I write the code with WinMain as the entry point, like this:

extern "C" {
    void __cdecl _CRT_INIT(void);
}

void __stdcall WinMain() {
    _CRT_INIT();

    int exitCode = WinMain(GetModuleHandle(NULL), NULL, GetCommandLineA(), SW_SHOWDEFAULT);

    exit(exitCode);
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdArg, int nCmd)
{
    ... whatever code is ...
}

However, I should encounter a compilation issue like this:

enter code here`error C2731: 'WinMain': You can not overload 

So, adjust the code with a few changes:

extern "C" {
    void __cdecl _CRT_INIT(void);
}

void __stdcall WinMainEntry() {
    _CRT_INIT();

    int exitCode = WinMain(GetModuleHandle(NULL), NULL, GetCommandLineA(), SW_SHOWDEFAULT);

    exit(exitCode);
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdArg, int nCmd)
{
    ... whatever code is ...
}

With link command:

cl main.cpp $(include_folders) /EHsc /link $(lib_folders) /OUT:program_$(my_arch).exe /SUBSYSTEM:WINDOWS /NODEFAULTLIB /ENTRY:WinMainEntry

This program runs without any issues. I made a few changes to the code:

void __stdcall WinMainEntry() {
    //_CRT_INIT();
    
    int exitCode = WinMain(GetModuleHandle(NULL), NULL, GetCommandLineA(), SW_SHOWDEFAULT);
    
    //exit(exitCode);
 }

Then, the output is very similar to yours.

So, I think the problem is that you overloaded the WinMain function. I don't encounter any issues when there's no Entry. The linker command is as follows:

cl main.cpp $(include_folders) /link $(lib_folders) /OUT:program_$(my_arch).exe /SUBSYSTEM:WINDOWS /NODEFAULTLIB /ENTRY:WinMainEntry

And the full source is :

#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);

//extern "C" {
//    void __cdecl _CRT_INIT(void);
//}

//void __stdcall WinMainEntry() {
    //_CRT_INIT();

//    int exitCode = WinMain(GetModuleHandle(NULL), NULL, GetCommandLineA(), SW_SHOWDEFAULT);

    //exit(exitCode);
//}

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();
}

The old comments :

Here’s the code for console mode with a slight modification:

#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.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 main() {
    SettingUpConsole(); 
    
    WNDCLASS wc = {0};
    wc.hInstance = GetModuleHandle(NULL);
    wc.lpszClassName = "Window Class";
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.lpfnWndProc = WndProc;
    wc.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(248, 248, 255));

    if (!RegisterClass(&wc))
        return 0;

    HWND WindowHandle = CreateWindowExA(
        0, wc.lpszClassName, "Custom Window",
        WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
        0, 0, 1000, 460, NULL, NULL, GetModuleHandle(NULL), NULL);

    if (!WindowHandle)
        return 0;
    
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    std::cout << "Exiting Loop." << std::endl;
    
    //for pause
    getchar();
    
    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();
}

Then, the Makefile will also need to be changed:

# 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 

!IF "$(my_arch)" == ""
my_arch = x86
!ENDIF

# Windows Kits 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 Kits Lib Folders
kit_lib_1 = "C:/Program Files (x86)/Windows Kits/10/Lib/10.0.22621.0/um/$(my_arch)"
kit_lib_2 = "C:/Program Files (x86)/Windows Kits/10/Lib/10.0.22621.0/ucrt/$(my_arch)"

# Combined Include Folders
include_folders = /I$(kit_include_1) /I$(kit_include_2) /I$(kit_include_3) /I$(kit_include_4) /I$(kit_include_5) /I$(include_folder)

# Combined Lib Folders
lib_folders = /LIBPATH:$(kit_lib_1) /LIBPATH:$(kit_lib_2) /LIBPATH:$(lib_folder) $(my_libraries)

# Build target 
build:
    cl main.cpp $(include_folders) /EHsc /link $(lib_folders) /OUT:program_$(my_arch).exe /SUBSYSTEM:CONSOLE /NODEFAULTLIB:LIBCMT

# Clean target
clean:
    del *.obj
    del program_$(my_arch).exe

To set up the development environment in MSVC 2022, follow these commands:

call cmd /K "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
nmake clean & nmake my_arch=x64
program_x64.exe

Now, I have an OpenGL test program with no issues. The results are as follows:

It is the WinMain version :

#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>

const char *GREEN = "\x1b[38;2;45;200;120m";

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void GeneratePixelFormat(HDC &dc);
void SetupRC(HDC hDC, GLuint nFontList);
void RenderScene(GLuint nFontList);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    WNDCLASS wc = {0};
    wc.hInstance = hInstance;
    wc.lpszClassName = "Window Class";
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.lpfnWndProc = WndProc;
    wc.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(248, 248, 255));

    if (!RegisterClass(&wc))
        return 0;

    HWND WindowHandle = CreateWindowExA(
        0, wc.lpszClassName, "Custom Window",
        WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
        0, 0, 1000, 460, NULL, NULL, hInstance, NULL);

    if (!WindowHandle)
        return 0;

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    OutputDebugString("Exiting Loop.\n"); 

    return (int)msg.wParam;
}

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:
    {
        OutputDebugString("Getting window's handle\n");
        dc = GetDC(WindowHandle);
        OutputDebugString("Setting pixel format\n");
        GeneratePixelFormat(dc);
        OutputDebugString("Creating rendering context...\n");
        rc = wglCreateContext(dc);
        OutputDebugString("Setting current context.\n");
        wglMakeCurrent(dc, rc);
        OutputDebugString("Sending message 30 times per second (30fps)\n");
        SetTimer(WindowHandle, 101, 33, NULL);
        SetupRC(dc, nFontList);
        return 0;
    }
    case WM_TIMER:
    {
        InvalidateRect(WindowHandle, NULL, FALSE);
        return 0;
    }
    case WM_CLOSE:
    {
        OutputDebugString("WM_CLOSE received. Destroying window...\n");
        ReleaseDC(WindowHandle, dc);
        wglMakeCurrent(dc, NULL);
        wglDeleteContext(rc);
        DestroyWindow(WindowHandle);
        return 0;
    }
    case WM_DESTROY:
    {
        OutputDebugString("WM_DESTROY received. Posting quit message...\n");
        PostQuitMessage(0);
        KillTimer(WindowHandle, 101);
        return 0;
    }
    case WM_SIZE:
    {
        RenderScene(nFontList);
        SwapBuffers(dc);
        ValidateRect(WindowHandle, NULL);
        return 0;
    }
    case WM_PAINT:
    {
        RenderScene(nFontList);
        SwapBuffers(dc);
        ValidateRect(WindowHandle, NULL);
        return 0;
    }
    default:
        return DefWindowProc(WindowHandle, uMsg, wParam, lParam);
    }
}

void GeneratePixelFormat(HDC &dc)
{
    static PIXELFORMATDESCRIPTOR pfd = {
        sizeof(PIXELFORMATDESCRIPTOR),
        1,
        PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
        PFD_TYPE_RGBA,
        32,
        0, 0, 0, 0, 0, 0,
        0, 0,
        0, 0, 0, 0, 0,
        16,
        0,
        0,
        0,
        0,
        0,
        0,
        0
    };

    int nPixelFormat = ChoosePixelFormat(dc, &pfd);
    SetPixelFormat(dc, nPixelFormat, &pfd);
}

void SetupRC(HDC hDC, GLuint nFontList)
{
    HFONT hFont;
    GLYPHMETRICSFLOAT agmf[128];
    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");

    hFont = CreateFontIndirect(&logfont);
    SelectObject(hDC, hFont);
    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);
    glColor3ub(0, 0, 255);
    glPushMatrix();
    glListBase(nFontList);
    glCallLists(6, GL_UNSIGNED_BYTE, "SDFSDF");
    glPopMatrix();
}

The result is :

This is Makefile for me :

# 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 

!IF "$(my_arch)" == ""
my_arch = x86
!ENDIF

# Windows Kits 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 Kits Lib Folders
kit_lib_1 = "C:/Program Files (x86)/Windows Kits/10/Lib/10.0.22621.0/um/$(my_arch)"
kit_lib_2 = "C:/Program Files (x86)/Windows Kits/10/Lib/10.0.22621.0/ucrt/$(my_arch)"

# Combined Include Folders
include_folders = /I$(kit_include_1) /I$(kit_include_2) /I$(kit_include_3) /I$(kit_include_4) /I$(kit_include_5) /I$(include_folder)

# Combined Lib Folders
lib_folders = /LIBPATH:$(kit_lib_1) /LIBPATH:$(kit_lib_2) /LIBPATH:$(lib_folder) $(my_libraries)

# Build target (exe file name: program_x86.exe or program_x64.exe)
build:
    cl main_window_mode.cpp $(include_folders) /EHsc /link $(lib_folders) /OUT:program_$(my_arch).exe /SUBSYSTEM:WINDOWS  /NODEFAULTLIB:LIBCMT

# Clean target
clean:
    del *.obj
    del program_$(my_arch).exe

Message Loop Should Handle WM_QUIT Properly,
Your message loop:

while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

GetMessage returns 0 when WM_QUIT is received, which exits the loop. This should normally cause WinMain to exit, terminating the process. However, if another thread is keeping the process alive, it may never fully terminate. Try modifying the message loop to log when WM_QUIT is received:

while (GetMessage(&msg, NULL, 0, 0) > 0) // Ensure GetMessage doesn't return -1 (error)
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}
printf("Main loop exited. Process should terminate.\n");

If Main loop exited. is printed but the process remains, something else is keeping it alive.

Also you can forcing Process Termination (ExitProcess) As a last resort, you can call ExitProcess(0); in WM_DESTROY:

case WM_DESTROY:
{
    printf("WM_DESTROY received. Posting quit message...\n");
    KillTimer(WindowHandle, 101);
    PostQuitMessage(0);
    
    // Last-resort cleanup if the process is still alive
    ExitProcess(0);
    return 0;
}

This forces termination of all threads and resources.

转载请注明原文地址:http://anycun.com/QandA/1744861782a88662.html