簡單的Windows API編程回顧

DirectX3D程序須要將3D場景繪製到一個windows窗口的客戶區中,所以複習一下簡單的Win32GUI編程是有必要的,對於DirectX3D編程,這些也就足夠了前端

1. Windows應用程序的基本機制

Windows應用程序採用事件驅動模型,windows會將用戶的行爲做爲事件發送給應用程序,應用程序啓動時就維護了一個優先級隊列,用於儲存windows發來的事件消息,應用程序循環檢查消息隊列,取出最前端的消息,發送給窗口處理函數進行處理,這就是消息循環。編程

  • 例子實現沒有遵守Petzold的經典方法去寫,而是將消息循環,窗口的初始化寫在了函數中,減少了WinMain的複雜度
#include <windows.h>

//Main Window handler, which we are going to create in the application
HWND hMain = NULL;

//Initialize a windows application
bool InitWindowsApp(HINSTANCE hInstance, int iShow);

//Window process which handles event the windows receives
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

//The message loop
int run();

//Entry
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int iCmdShow)
{
    //if initialization fails, pop a msgbox showing error msg and then exit
    if (!InitWindowsApp(hInstance, iCmdShow))
    {
        ::MessageBox(0, TEXT("Init Failed"), TEXT("ERROR"), MB_OK);
        return 0;
    }

    //Enter message loop
    //the program flow is passed to function run, tricky move in game engines
    return run();
}

bool InitWindowsApp(HINSTANCE hInstance, int iCmdShow)
{
    //wndclass contains characteristics of the new window
    WNDCLASS wc;

    wc.style        = CS_HREDRAW | CS_VREDRAW; 
    wc.lpfnWndProc  = WndProc;
    wc.cbClsExtra   = 0;
    wc.cbWndExtra   = 0;
    wc.hInstance    = hInstance;
    wc.hIcon        = ::LoadIcon(0, IDI_APPLICATION);
    wc.hCursor      = ::LoadCursor(0, IDC_ARROW);
    wc.hbrBackground= static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH));
    wc.lpszMenuName = 0;
    wc.lpszClassName= TEXT("Hello");

    //Register the window class so we can create windows with suck characteristics
    if( !RegisterClass(&wc) )
    {
        ::MessageBox(0, TEXT("RegisterClass Failed!"), TEXT("Error"), MB_OK);
        return false;
    }

    //We have our windows class registered, now we can create our window via CreateWindow(),
    //The function returns handler of the new window, in Init of the new winApp, we store it in
    //MainWindowHandler, after that, we can reference the main windows via the handler

    hMain = ::CreateWindow(TEXT("Hello"),
                           TEXT("Hello"),
                           WS_OVERLAPPEDWINDOW,
                           CW_USEDEFAULT,
                           CW_USEDEFAULT,
                           CW_USEDEFAULT,
                           CW_USEDEFAULT,
                           NULL,
                           NULL,
                           hInstance,
                           NULL);
    if( hMain == NULL )
    {
        ::MessageBox(NULL, TEXT("CreateWindow Failed"), TEXT("ERROR"), MB_OK);
        return false;
    }

    //Finally the new windows is created, we can now call ShowWindow and UpdateWindow
    //to Show and Update the window via passing the window handler to the functions
    ::ShowWindow(hMain, iCmdShow); 
    ::UpdateWindow(hMain);

    //Init finished
    return true;
}

//msg loop
int run()
{
    MSG msg;
    ::ZeroMemory(&msg, sizeof(MSG));

    //GetMessage returns 0 on WM_QUIT
    while( ::GetMessage(&msg, 0, 0, 0) )
    {
        //Translate the msg and dispatch it to WndProc
        ::TranslateMessage(&msg);
        ::DispatchMessage(&msg);
    }
    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch( msg )
    {
    case WM_LBUTTONDOWN:
        ::MessageBox(0, TEXT("Hello, World!"), TEXT("Hello"), MB_OK);
        return 0;
    case WM_KEYDOWN:
        if( wParam == VK_ESCAPE )
        {
            ::DestroyWindow(hMain);
        }
        return 0;
    case WM_DESTROY:
        ::PostQuitMessage(0);
        return 0;
    }
    return ::DefWindowProc(hWnd, msg, wParam, lParam);
}

分析
整個程序的入口點位於WinMain,建立窗口所使用的窗口處理函數爲WndProc,InitWindowsApp完成了窗口類的註冊和窗口的建立及顯示,run實現了windows消息循環windows

  • 如以前所述,WinMain比較簡單,只處理了兩件事
    • 調用InitWindowsApp初始化應用程序環境
    • 調用run進入消息循環
  • InitWindowsApp作了三件事
    • 註冊窗口類
      • WNDCLASS定義了一個窗口類,爲其指定了默認參數,下一部會依照這些參數來建立一個窗口
      • RegisterClass將咱們定義的窗口類註冊給系統,供建立時使用
    • 建立窗口
      • CreateWindow參照咱們剛剛註冊的窗口類建立了一個窗口
    • 顯示窗口
      • DisplayWindow和UpdateWindow將窗口顯示出來並刷新一次
  • run實現了消息循環
    • GetMessage獲取消息,當獲取到WM_QUIT時返回0,退出消息循環
      • TranslateMessage將虛擬鍵消息轉換爲字符消息
      • DispatchMessage將消息轉發給窗口處理函數進行處理
  • WndProc是咱們定義的窗口處理函數,用於處理run發送來的消息
    • switch針對不一樣消息作出不一樣的響應
    • switch中未響應的消息交給默認窗口處理程序DefWndProc處理
  • 改進的消息循環
int run()
{
    MSG msg;
    ::ZeroMemory(&msg, sizeof(MSG));

    while( true )
    {
        if( PeekMessage(&msg, 0, 0, 0, 0) )
        {
            if( msg.message == WM_QUIT )
            {
                break;
            }

            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
        }
        ::MessageBox(0, TEXT("Outside of msg"), TEXT("Test"), MB_OK);
        //TODO: Game Code
    }
    return msg.wParam;
}
  • 遊戲程序多數不是事件驅動的,須要不斷更新,消息隊列爲空時,GetMessage會進入阻塞,等待有消息在進行處理,而PeekMessage在消息隊列爲空時會馬上返回,因而咱們能夠將消息循環進行上述更改,讓遊戲代碼不斷執行更新,當PeekMessage得到消息時,再交由窗口處理程序進行處理,實際上,通常狀況下窗口處理函數的實現會將消息直接轉發給遊戲引擎的消息處理函數進行處理

Written with StackEdit.app

相關文章
相關標籤/搜索