【问题标题】:How to properly close/terminate a simple Windows app如何正确关闭/终止一个简单的 Windows 应用程序
【发布时间】:2021-05-19 15:17:49
【问题描述】:

拥有如此简单的 DirectX 应用程序:

#include <d3dx9.h>
#include <tchar.h>
#include <chrono>
#include "Utils.h"

//#pragma comment (lib, "d3d9.lib")
//#pragma comment (lib, "d3dx9.lib")

using namespace std::chrono_literals;
using namespace std::chrono;

const wchar_t g_szClassName[] = _T("myWindowClass");

// we use a fixed timestep of 1 / (60 fps) = 16 milliseconds
//constexpr std::chrono::nanoseconds timestep(16ms);
constexpr std::chrono::nanoseconds timestep(1000ms);

// Step 4: the Window Procedure
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

wchar_t msgbuf[100];
int loop_cnt = 0;

bool handle_events() {
    // poll for events

    return false; // true if the user wants to quit the game
}

void update(long long lag) {
    // update game logic here
    swprintf_s(msgbuf, _T("update - loop_cnt: %d\n"), loop_cnt++);
    OutputDebugString(msgbuf);

}

void render() {
    // render stuff here
}

VOID OnPaint(HDC hdc) {
}

// ------------ D3D ------------

IDirect3D9* pD3D;
IDirect3DDevice9* pDev;
IDirect3DVertexBuffer9* pVB;

const DWORD OURVERT_FVF = D3DFVF_XYZRHW | D3DFVF_DIFFUSE;

struct OurVertex {
    float x, y, z;  // pozycja
    float rhw;          // komponent rhw
    D3DCOLOR color; // kolor
};

// -----------------------------

int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR lpCmdLine, _In_ int nCmdShow) {

    using clock = std::chrono::high_resolution_clock;

    WNDCLASSEX wc;
    HWND hWnd;
    MSG Msg = {0};

    std::chrono::nanoseconds lag(0ns);
    auto start_time = clock::now();
    bool quit_game = false;

    //Step 1: Registering the Window Class
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = g_szClassName;
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

    if (!RegisterClassEx(&wc)) {
        MessageBox(NULL, _T("Window Registration Failed!"), _T("Error!"), MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    // Step 2: Creating the Window
    hWnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        g_szClassName,
        _T("The title of my window"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 
        CW_USEDEFAULT, 
        480,    // initial x size
        640,    // initial y size
        NULL, 
        NULL, 
        hInstance, 
        NULL
    );

    OutputDebugString(_T("--> WinMain <-- \n"));

    if (hWnd == NULL) {
        MessageBox(NULL, _T("Window Creation Failed!"), _T("Error!"), MB_ICONEXCLAMATION | MB_OK);
        return 0;
    }

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    // ------------ D3D ------------

    pD3D = Direct3DCreate9(D3D_SDK_VERSION);
    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.Windowed = true;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;

    OurVertex verts[] = {
        { 20.0f, 20.0f, 0.5f, 1.0f, 0xffff0000, },
        { 100.0f, 20.0f, 0.5f, 1.0f, 0xff00ff00, },
        { 20.0f, 100.0f, 0.5f, 1.0f, 0xff00ff55, },
        { 100.0f, 100.0f, 0.5f, 1.0f, 0xff0000ff},
    };

    pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &pDev);

    HRESULT hr = pDev->CreateVertexBuffer(4*sizeof(OurVertex), D3DUSAGE_DYNAMIC, OURVERT_FVF, D3DPOOL_DEFAULT, &pVB, 0);
    check_HRESULT(_T("CreateVertexBuffer"), hr);

    void* data;

    pVB->Lock(0, 4*sizeof(OurVertex), &data, D3DLOCK_DISCARD);
    memcpy(data, (void*)verts, sizeof(verts));
    pVB->Unlock();

    // -----------------------------


    // Step 3: The Message Loop
    while (TRUE) {

        while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE) > 0) {
            TranslateMessage(&Msg);
            DispatchMessage(&Msg);
        }

        if (Msg.message == WM_QUIT)
            break;

        //  Run game logic

            auto current_time = clock::now();
            auto frame_time = current_time - start_time;
            start_time = current_time;
            lag += duration_cast<nanoseconds>(frame_time);

            // update game logic as lag permits
            while (lag >= timestep) {
                lag -= timestep;

                pDev->Clear(0, 0, D3DCLEAR_TARGET, 0xff000000, 1, 0);
                pDev->BeginScene();
                pDev->SetFVF(OURVERT_FVF);
                pDev->SetStreamSource(0, pVB, 0, sizeof(OurVertex));
                pDev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
                pDev->EndScene();
                pDev->Present(0, 0, 0, 0);

                update(lag.count()); // update at a fixed rate each time
            }

    }

    OutputDebugString(_T("--> AFTER MSG LOOP END <-- \n"));

    return Msg.wParam;
}

// Step 4: the Window Procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {

    switch (msg) {

        case WM_CLOSE:
            DestroyWindow(hwnd);
            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }

    return 0;
}

当我运行调试(没有定义任何刹车点)时,我得到了输出:

--> WinMain <-- 
update - loop_cnt: 0
update - loop_cnt: 1
update - loop_cnt: 2
update - loop_cnt: 3
update - loop_cnt: 4
update - loop_cnt: 5
...

程序成功地按预期显示了一个彩色矩形。但是,这里出了点问题,因为当我单击应用程序窗口右上角的标准“X”关闭按钮时,窗口关闭但程序似乎没有正确结束。我仍然在 Visual Studio 的“输出”窗口中收到 update - loop_cnt: # 消息,并且没有提示正确程序终止的 --&gt; AFTER MSG LOOP END &lt;-- 消息。

而且,当我再次尝试运行调试时,我得到一个错误:

LINK : fatal error LNK1168: cannot open ...Game_Loop_Windows_1.exe for writing
...
MSB6006: "link.exe" exited with code 1168.
1>Done building project "Game_Loop_Windows_1.vcxproj" -- FAILED

代码有什么问题?

PS。我在 Windows 10 上使用 Microsoft Visual Studio Community 2019

【问题讨论】:

    标签: c++ winapi visual-c++


    【解决方案1】:

    消息循环可能永远不会终止。

    while (PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE) > 0) {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
    
    if (Msg.message == WM_QUIT)
        break;
    

    您正在处理所有消息,但只检查最后一个是否是WM_QUIT 消息。相反,您需要检查所有消息并正确退出循环,如下所示:

    bool running{true};
    
    while (running) {
        while (running && PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE) > 0) {
            TranslateMessage(&Msg);
            DispatchMessage(&Msg);
            running = Msg.message != WM_QUIT;
        }
    
        // Not strictly required; you can run another render loop and have
        // the outermost loop exit when running is false
        if (!running)
            break;
    

    链接错误仅表示该进程仍在运行。无法写入映射到进程的可执行映像。这就是链接器所抱怨的。

    【讨论】:

    • 谢谢回复。我提供的代码无效,这很奇怪。 Direct X 教程directxtutorial.com/Lesson.aspx?lessonid=9-1-4 站点上提供了非常相似的代码(与我的问题相同,仅检查最后一条消息)...
    • 这是否意味着那里提供的代码也无效?
    • 如果它是相同的代码,那么它会因为同样的原因被破坏。这个缺陷很容易被忽略,因为一切似乎都正常。直到你重新编译。猜猜这只是编程第一法则的另一种表现:你在互联网上找到的每一个代码都是垃圾。
    • 是的,你是对的朋友:-) 谢谢你指出我的问题。有一件事仍然让我想知道 - 发送 WM_QUIT 消息后系统怎么还会发送其他额外的消息?
    • WM_QUIT is a low-priority message 所以唯一的尾随消息也需要是低优先级的。 WM_NCMOUSEMOVE 就是这样一个候选者或计时器消息。正如documentation 解释的那样,“当线程从其消息队列中检索WM_QUIT 消息时,它应该退出其消息循环”。此答案中的代码仅遵循合同。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-13
    • 1970-01-01
    • 2019-12-17
    • 1970-01-01
    • 2016-08-01
    相关资源
    最近更新 更多