Windows編程簡介-DX的Windows編程基礎

要使用Direct3D API(應用程序編程接口),有必要創建一個帶有主窗口的Windows(Win32)應用程序,我們將在其中呈現我們的3D場景。本文可作爲使用本機Win32 API編寫Windows應用程序的介紹。通俗講,Win32 API是一組低級函數和結構,以C編程語言向我們公開接口,使我們能夠創建Windows應用程序。例如,要定義一個窗口類,我們填寫一個Win32 API WNDCLASS結構的實例;創建一個窗口,我們使用Win32 API的CreateWindow函數; 爲了通知Windows顯示特定的窗口,我們使用Win32 API函數ShowWindow

Windows編程是一個巨大的課題,本文僅介紹使用Direct3D所需的部分。對於Windows編程感興趣的讀者可以參考Charles Petzold編寫的《Programming Windows》第5版。使用Microsoft技術的另一個寶貴資源是MSDN庫,它通常包含在Microsoft的Visual Studio中,也可以通過www.msdn.microsoft.com在線閱讀。一般來說,如果您想要了解有關Win32函數或結構的更多信息,請轉至MSDN並搜索該函數或結構以獲取完整文檔。如果本文中提到Win32函數或結構,並且沒有詳細說明它,請你在MSDN中查看函數的含義。

學習目標:
1.理解Windows編程中使用的事件驅動編程模型。
2.學習創建使用Direct3D必需的Windows應用程序所需的底層代碼。

爲了避免混淆,我們將使用大寫’W’來指代Windows操作系統,我們將使用小寫’w’來指代在Windows中運行的特定窗口。

1、概述

顧名思義,Windows編程的主要主題之一就是編程窗口。Windows應用程序的許多組件都是窗口,例如主應用程序窗口,菜單,工具欄,滾動條,按鈕和其他對話框控件。因此,Windows應用程序通常由多個窗口組成。 在開始更完整的討論之前我們先來了解一下Windows編程概念的簡要概述。

1.1資源

在Windows中,多個應用程序可以同時運行。因此,硬件資源(如CPU週期,內存,甚至顯示器屏幕)必須在多個應用程序之間共享。爲了防止由於多個應用程序在沒有任何組織的情況下訪問/修改資源而導致混亂,Windows應用程序不能直接訪問硬件。Windows的主要工作之一是管理目前實例化的應用程序並處理它們之間的資源分配。因此,爲了讓我們的應用程序做一些可能影響另一個正在運行的應用程序的事情,它必須通過Windows實現。例如,要顯示一個窗口,你必須調用Win32 API函數ShowWindow; 您也無法直接讀寫內存。

1.2事件,消息隊列,消息和消息循環

Windows應用程序遵循事件驅動的編程模型。通常情況下,Windows應用程序掛起並等待事件發生。事件可以通過多種方式產生; 一些常見的例子是按鍵,鼠標點擊,以及窗口創建,調整大小,移動,關閉,最小化,最大化或變得可見。

發生事件時,Windows會嚮應用程序發送消息,並將消息添加到應用程序的消息隊列中,消息隊列只是存儲應用程序消息的優先級隊列。 應用程序不斷地檢查消息隊列中的消息循環中的消息,並且當它接收到消息時,它會將消息分派到消息所針對的特定窗口的窗口過程中。 (請記住,應用程序可以包含多個窗口。)每個窗口都有一個關聯的函數,稱爲窗口過程。窗口過程是我們實現的函數,它包含要響應特定消息而執行的代碼。例如,我們可能想在按Escape鍵時銷燬一個窗口。在我們的窗口過程中,我們會寫:

case WM_KEYDOWN:
    if(wParam == VK_ESCAPE)
        DestroyWindow(ghMainWnd);
    return 0;

窗口不處理的消息應該被轉發到默認的窗口過程,然後處理該消息。Win32 API提供默認的窗口過程,稱爲DefWindowProc

總而言之,用戶或應用程序會做一些事情來產生事件。 操作系統發現事件針對的應用程序,並嚮應用程序發送一條消息作爲迴應。 然後將消息添加到應用程序的消息隊列中。應用程序不斷地檢查消息隊列中的消息。 當它收到一條消息時,應用程序將它分派到消息所針對的窗口的窗口過程中。 最後,窗口過程響應該消息執行指令。

圖(A.1)總結了事件驅動的編程模型


圖A.1 事件驅動的編程模型。

1.3 GUI

大多數Windows程序都提供了一個用戶可以使用的GUI(圖形用戶界面)。一個典型的Windows應用程序有一個主窗口,一個菜單,工具欄和其他一些控件。圖A.2顯示並標識了一些常見的GUI元素。 對於Direct3D遊戲編程,我們不需要花哨的GUI。 事實上,我們所需要的只是一個主窗口,客戶區將用於渲染我們的3D世界。


圖A.2 典型的Windows應用程序GUI。客戶區是應用程序的整個大型白色矩形空間。通常,這個區域是用戶查看大部分程序輸出的地方。當我們編寫Direct3D應用程序時,我們將3D場景渲染到窗口的客戶區。

1.4 Unicode

本質上,Unicode(http://unicode.org/)使用16位值來表示一個字符。 這使我們能夠代表更大的字符集來支持國際字符和其他符號。 對於C ++中的Unicode,我們使用寬字符類型wchar_t。在32位和64位Windows中,wchar_t是16位。當使用寬字符時,我們必須在一個字符串前加大寫字母L; 例如:

const wchar_t* wcstrPtr = L“Hello, World!”;

L告訴編譯器將字符串視爲一個寬字符的字符串(即爲wchar_t而不是char)。另一個重要的問題是我們需要使用字符串函數的寬字符版本。例如,要獲得字符串的長度,我們需要使用wcslen而不是strlen; 複製一個字符串我們需要使用wcscpy而不是strcpy; 爲了比較兩個字符串,我們需要使用wcscmp而不是strcmp。這些函數的寬字符版本使用wchar_t指針而不是char指針。C ++標準庫還提供了字符串類的寬字符版本:std :: wstring。Windows頭文件WinNT.h也定義了:

typedef wchar_t WCHAR; // wc, 16-bit UNICODE character

2、基本的WINDOWS應用程序

以下是一個功能齊全但簡單的Windows程序的代碼。儘量結合注視讀懂代碼。之後我會對代碼進行詳細講解。建議您使用VS創建項目,手動輸入代碼,編譯代碼並將其作執行。請注意,對於Visual C ++,您必須創建一個“Win32應用程序項目”,而不是“Win32控制檯應用程序項目”。

//=====================================================================
// Win32Basic.cpp by Frank Luna (C) 2008 All Rights Reserved.
//
// Illustrates the minimal amount of the Win32 code needed for
// Direct3D programming.
//=====================================================================
// Include the windows header file; this has all the Win32 API
// structures, types, and function declarations we need to program
// Windows.
#include <windows.h>
// The main window handle; this is used to identify a
// created window.
HWND ghMainWnd = 0;
// Wraps the code necessary to initialize a Windows
// application. Function returns true if initialization
// was successful, otherwise it returns false.
bool InitWindowsApp(HINSTANCE instanceHandle, int show);
// Wraps the message loop code.
int Run();
// The window procedure handles events our window receives.
LRESULT CALLBACK
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
// Windows equivalant to main()
int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR pCmdLine, int nShowCmd)
{
// First call our wrapper function (InitWindowsApp) to create
// and initialize the main application window, passing in the
// hInstance and nShowCmd values as arguments.
if(!InitWindowsApp(hInstance, nShowCmd))
return 0;
// Once our application has been created and initialized we
// enter the message loop. We stay in the message loop until
// a WM_QUIT mesage is received, indicating the application
// should be terminated.
return Run();
}
bool InitWindowsApp(HINSTANCE instanceHandle, int show)
{
// The first task to creating a window is to describe some of its
// characteristics by filling out a WNDCLASS structure.
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = instanceHandle;
wc.hIcon = LoadIcon(0, IDI_APPLICATION);
wc.hCursor = LoadCursor(0, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = 0;
wc.lpszClassName = L“BasicWndClass“;
// Next, we register this WNDCLASS instance with Windows so
// that we can create a window based on it.
if(!RegisterClass(&wc))
{
MessageBox(0, L“RegisterClass FAILED“, 0, 0);
return false;
}
// With our WNDCLASS instance registered, we can create a
// window with the CreateWindow function. This function
// returns a handle to the window it creates (an HWND).
// If the creation failed, the handle will have the value
// of zero. A window handle is a way to refer to the window,
// which is internally managed by Windows. Many of the Win32 API
// functions that operate on windows require an HWND so that
// they know what window to act on.
ghMainWnd = CreateWindow(
L“BasicWndClass”, // Registered WNDCLASS instance to use.
L“Win32Basic”, // window title
WS_OVERLAPPEDWINDOW, // style flags
CW_USEDEFAULT, // x-coordinate
CW_USEDEFAULT, // y-coordinate
CW_USEDEFAULT, // width
CW_USEDEFAULT, // height
0, // parent window
0, // menu handle
instanceHandle, // app instance
0); // extra creation parameters
if(ghMainWnd == 0)
{
MessageBox(0, L“CreateWindow FAILED“, 0, 0);
return false;
}
// Even though we just created a window, it is not initially
// shown. Therefore, the final step is to show and update the
// window we just created, which can be done with the following
// two function calls. Observe that we pass the handle to the
// window we want to show and update so that these functions know
// which window to show and update.
ShowWindow(ghMainWnd, show);
UpdateWindow(ghMainWnd);
return true;
}
int Run()
{
MSG msg = {0};
// Loop until we get a WM_QUIT message. The function
// GetMessage will only return 0 (false) when a WM_QUIT message
// is received, which effectively exits the loop. The function
// returns -1 if there is an error. Also, note that GetMessage
// puts the application thread to sleep until there is a
// message.
BOOL bRet = 1;
while((bRet = GetMessage(&msg, 0, 0, 0)) != 0)
{
if(bRet == -1)
{
MessageBox(0, L“GetMessage FAILED“, L“Error“, MB_OK);
break;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
LRESULT CALLBACK
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
// Handle some specific messages. Note that if we handle a
// message, we should return 0.
switch(msg)
{
// In the case the left mouse button was pressed,
// then display a message box.
case WM_LBUTTONDOWN:
MessageBox(0, L“Hello, World“, L“Hello“, MB_OK);
return 0;
// In the case the escape key was pressed, then
// destroy the main application window.
case WM_KEYDOWN:
if(wParam == VK_ESCAPE)
DestroyWindow(ghMainWnd);
return 0;
// In the case of a destroy message, then send a
// quit message, which will terminate the message loop.
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
// Forward any other messages we did not handle above to the
// default window procedure. Note that our window procedure
// must return the return value of DefWindowProc.
return DefWindowProc(hWnd, msg, wParam, lParam);
}


圖A.3 之前程序的屏幕截圖。請注意,當您在窗口的客戶區中按下鼠標左鍵時,會出現消息框。也可以按退出鍵退出程序。

3、講解基本的WINDOWS應用程序

我們逐步分析代碼和被調用的函數。參照上一節中“基本Windows應用程序”的代碼。

3.1 頭文件包含、全局變量和原型

我們做的第一件事是包含windows.h頭文件。通過包含windows.h文件,我們可以獲得使用Win32 API基本元素所需的數據結構,數據類型和函數聲明。

#include <windows.h>

第二個語句是一個HWND類型的全局變量的實例化。 這代表“處理窗口”或“窗口句柄”。在Windows編程中,我們經常使用句柄來引用Windows內部維護的對象。 在這個示例中,我們將使用HWND來引用由Windows維護的我們的主應用程序窗口。 我們需要保持我們的窗口的句柄,因爲對API的許多調用都要求我們傳遞我們希望API調用執行的窗口句柄。 例如,調用UpdateWindow接受一個HWND類型的參數,用於指定要更新的窗口。 如果我們沒有傳入句柄,函數就不知道要更新哪個窗口。

HWND ghMainWnd = 0;

接下來的三行是函數聲明。簡而言之,InitWindowsApp創建並初始化我們的主應用程序窗口;Run封裝我們的應用程序的消息循環;WndProc是我們主窗口的窗口過程。當我們遇到被調用的地方時,我們將更詳細地研究這些功能。

bool InitWindowsApp(HINSTANCE instanceHandle, int show);
int Run();
LRESULT CALLBACK
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

3.2、WinMain

WinMain與C ++編程中的main函數功能相同。WinMain的原型如下:

int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR pCmdLine, int nShowCmd)

1.hInstance:處理當前的應用程序實例。它用作識別和引用這個應用程序的一種方式。請記住,可能有多個Windows應用程序併發運行,因此需要能夠引用每個應用程序。
2.hPrevInstance:在Win32編程中不使用,爲零。
3.pCmdLine:用於運行程序的命令行參數字符串。
4.nCmdShow:指定如何顯示應用程序。以當前大小和位置顯示窗口,最大化和最小化的一些常見命令分別是SW_SHOW,SW_SHOWMAXIMIZEDSW_SHOWMINIMIZED。有關show命令的完整列表,請參閱MSDN庫。

如果WinMain成功,它應該返回WM_QUIT消息的wParam成員。如果函數在沒有進入消息循環的情況下退出,它應該返回零。WINAPI標識符定義爲:

#define WINAPI __stdcall

這指定了函數的調用約定,這意味着函數參數如何放置在堆棧上。

3.3 WNDCLASS和註冊

WinMain內部我們調用了函數InitWindowsApp。可想而知,這個函數完成了我們程序的所有初始化。讓我們仔細看看這個功能及其實現。InitWindowsApp返回truefalse:如果初始化成功則返回true,否則返回false。在WinMain定義中,我們將應用程序實例和show命令變量的副本作爲參數傳遞給InitWindowsApp。兩者都從WinMain參數列表中獲取。

if(!InitWindowsApp(hInstance, nShowCmd))

手動初始化窗口的第一個任務是通過填寫WNDCLASS(窗口類)結構來描述窗口的一些基本屬性。其定義是:

typedef struct _WNDCLASS {
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HANDLE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
} WNDCLASS;

1.style:指定類樣式。在我們的例子中,我們使用CS_HREDRAWCS_VREDRAW。這兩個位標誌表明,當水平或垂直窗口大小改變時,窗口將被重新繪製。有關具有說明的各種樣式的完整列表,請參閱MSDN庫。

wc.style = CS_HREDRAW | CS_VREDRAW;

2.lpfnWndProc:指向與此WNDCLASS實例關聯的窗口過程函數的指針。 基於此WNDCLASS實例創建的Windows將使用此窗口過程。 因此,要使用相同的窗口過程創建兩個窗口,只需基於相同的WNDCLASS實例創建兩個窗口即可。 如果要使用不同的窗口過程創建兩個窗口,則需要爲兩個窗口中的每個窗口填寫不同的WNDCLASS實例。窗口過程函數在3.6中解釋。

wc.lpfnWndProc = WndProc;

3.cbClsExtra和cbWndExtra:這是您可以用於自己的目的的額外內存插槽。我們的程序不需要任何額外的空間,因此將這兩個設置爲零。

wc.cbClsExtra = 0;
wc.cbWndExtra = 0;

4.hInstance:該字段是應用程序實例的句柄。回調應用程序實例句柄最初是通過WinMain傳入的。

wc.hInstance = instanceHandle;

5.hIcon:在這裏你指定一個圖標的句柄用於使用這個窗口類創建的窗口。 您可以使用自己設計的圖標,但有幾個內置圖標供您選擇; 有關詳細信息,請參閱MSDN庫。以下使用默認應用程序圖標:

wc.hIcon = LoadIcon(0, IDI_APPLICATION);

6.hCursor:與hIcon類似,在這裏你指定一個光標的句柄,當光標在窗口的客戶區域上時使用。同樣,有幾個內置的光標; 有關詳細信息,請參閱MSDN庫。 以下代碼使用標準的“箭頭”光標。

wc.hCursor = LoadCursor(0, IDC_ARROW);

7.hbrBackground:該字段用於指定畫筆的句柄,該句柄指定窗口客戶區的背景顏色。 在我們的示例代碼中,我們調用了Win32函數GetStockObject,該函數返回預構建的白色筆刷的句柄; 有關其他類型的內置畫筆,請參閱MSDN庫。

wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);

8.lpszMenuName:指定窗口的菜單。 由於我們的應用程序中沒有菜單,我們將其設置爲零。

wc.lpszMenuName = 0;

9.lpszClassName:指定我們正在創建的窗口類結構的名稱。 這可以是任何你想要的。 在我們的應用程序中,我們將其命名爲“BasicWndClass”。該名稱僅用於標識類結構,以便稍後通過名稱引用它。

wc.lpszClassName = L“BasicWndClass“;

一旦我們填寫了一個WNDCLASS實例,我們需要在Windows上註冊它,以便我們可以基於它創建窗口。這是通過RegisterClass函數完成的,該函數採用指向WNDCLASS結構的指針。 失敗時該函數返回零。

if(!RegisterClass(&wc))
{
    MessageBox(0, L“RegisterClass FAILED“, 0, 0);
    return false;
}

3.4創建和顯示窗口

在Windows中註冊了WNDCLASS變量後,我們可以根據該類描述創建一個窗口。我們可以通過我們給它的類名引用一個已註冊的WNDCLASS實例(lpszClassName)。我們用來創建窗口的函數是CreateWindow函數,聲明如下:

HWND CreateWindow(
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu,
    HANDLE hInstance,
    LPVOID lpParam
);

1.lpClassName:註冊的WNDCLASS結構的名稱,它描述了我們要創建的窗口的一些屬性。
2.lpWindowName:我們想給我們的窗口的名字;這也是出現在窗口標題欄中的名稱。
3.dwStyle:定義窗口的樣式。WS_OVERLAPPEDWINDOW是幾個標誌的組合:WS_OVERLAPPED,WS_CAPTION,WS_SYSMENU,WS_THICKFRAME,WS_MINIMIZEBOX和WS_MAXIMIZEBOX。這些標誌的名稱描述了它們生成的窗口的特徵。請參閱MSDN庫以獲取樣式的完整列表。
4.x:窗口左上角相對於屏幕的x位置。您可以爲此參數指定CW_USEDEFAULT,並且Windows將選擇一個適當的默認值。
5.y:窗口左上角相對於屏幕的y位置。您可以爲此參數指定CW_USEDEFAULT,並且Windows將選擇一個適當的默認值。
6.nWidth:窗口的寬度(以像素爲單位)。您可以爲此參數指定CW_USEDEFAULT,並且Windows將選擇一個適當的默認值。
7.nHeight:窗口的高度,以像素爲單位。您可以爲此參數指定CW_USEDEFAULT,並且Windows將選擇一個適當的默認值。
8.hWndParent:處理一個窗口,該窗口是該窗口的父窗口。我們的窗口與任何其他窗口沒有任何關係,因此我們將此值設置爲零。
9.hMenu:菜單的句柄。我們的程序不使用菜單,因此我們爲該字段指定0。
10.hInstance:處理窗口將要關聯的應用程序。
11.lpParam:指向要用於WM_CREATE消息處理程序的用戶定義數據的指針。 WM_CREATE消息在創建時發送到窗口,但在CreateWindow返回之前。一個窗口處理WM_CREATE消息,如果它想在創建時做某些事情(例如,初始化)。

當我們指定窗口位置的(x,y)座標時,它們相對於屏幕的左上角。 此外,正x軸像往常一樣向右運行,但正y軸向下。圖A.4顯示了這個座標系,稱爲屏幕座標或屏幕空間。


圖A.4 屏幕空間。CreateWindow向它創建的窗口(HWND)返回一個句柄。 如果創建失敗,則句柄將具有零值(空句柄)。 請記住,句柄是指由Windows管理的窗口的一種方式。 許多API調用都需要一個HWND,以便它知道要操作的窗口。

ghMainWnd = CreateWindow(L“BasicWndClass“, L“Win32Basic“,
    WS_OVERLAPPEDWINDOW,
    CW_USEDEFAULT, CW_USEDEFAULT,
    CW_USEDEFAULT, CW_USEDEFAULT,
    0, 0, instanceHandle, 0);
if(ghMainWnd == 0)
{
    MessageBox(0, L“CreateWindow FAILED“, 0, 0);
    return false;
}

InitWindowsApp函數中的最後兩個函數調用與顯示窗口有關。 首先調用ShowWindow並傳入我們新創建的窗口的句柄,以便Windows知道要顯示哪個窗口。 我們還傳入一個整數值,該值定義了窗口最初如何顯示(例如,最小化,最大化等)。 這個值應該是nShowCmd,它是WinMain的一個參數。顯示窗口後,我們應該刷新它。UpdateWindow執行此操作; 它需要一個參數來處理我們希望更新的窗口。

ShowWindow(ghMainWnd, show);
UpdateWindow(ghMainWnd);

到目前爲止我們在InitWindowsApp中所做的一切就完成了初始化;我們返回true以表示一切順利。
3.5消息循環
在成功完成初始化後,我們可以開始程序的核心“消息循環”。在我們的基本Windows應用程序中,我們將消息循環封裝在一個名爲Run的函數中。

int Run()
{
    MSG msg = {0};
    BOOL bRet = 1;
    while((bRet = GetMessage(&msg, 0, 0, 0)) != 0)
    {
        if(bRet == -1)
        {
            MessageBox(0, L“GetMessage FAILED“, L“Error“, MB_OK);
            break;
        }
        else
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}

在Run中完成的第一件事是一個名爲msg類型MSG的變量的實例化,它是表示Windows消息的結構。其定義如下:

typedef struct tagMSG {
    HWND hwnd;
    UINT message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD time;
    POINT pt;
} MSG;

1.hwnd:窗口句柄要接收消息的窗口的句柄。
2.message:標識消息的預定義常量值(例如,WM_QUIT)。
3.wParam:關於消息的額外信息。這取決於具體的消息。
4.lParam:關於消息的額外信息。這取決於具體的消息。
5.time:郵件發佈的時間。
6.pt:消息發出時鼠標光標位於屏幕中的(x,y)座標。

接下來,我們進入消息循環。 GetMessage函數從消息隊列中檢索消息,並用消息的細節填充msg參數。爲此,GetMessage的第二,第三和第四個參數可以設置爲零。如果GetMessage中發生錯誤,則GetMessage返回一個值。如果收到WM_QUIT消息,則GetMessage返回0,從而終止消息循環。如果GetMessage返回任何其他值,則會調用另外兩個函數:TranslateMessage和DispatchMessage。TranslateMessage有Windows執行一些鍵盤翻譯; 特別是虛擬鍵字符消息。DispatchMessage最後將消息發送到適當的窗口過程。

如果應用程序通過WM_QUIT消息成功退出,則WinMain函數應該返回WM_QUIT消息的wParam(退出代碼)。

3.6窗口過程

我們之前提到過,窗口過程就是我們編寫我們想要執行的代碼,以響應我們窗口收到的消息。在基本Windows應用程序中,我們將窗口過程命名爲WndProc,它的原型如下:

LRESULT CALLBACK
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

該函數返回一個LRESULT類型的值(定義爲一個整數),該值指示函數的成功或失敗。 CALLBACK標識符指定函數是回調函數,這意味着Windows將在程序的代碼空間外調用此函數。 正如您從基本Windows應用程序源代碼中所看到的那樣,我們從來沒有明確地調用窗口過程,“當窗口需要處理消息時,Windows會爲我們調用它。

該窗口過程在其簽名中有四個參數:
1.hWnd:接收消息的窗口的句柄。
2.msg:標識特定消息的預定義值。 例如,退出消息被定義爲WM_QUIT。 前綴WM代表“窗口消息”。有超過一百個預定義的窗口消息; 有關詳細信息,請參閱MSDN庫。
3.wParam:關於依賴於特定消息的消息的附加信息。
4.lParam:關於依賴於特定消息的消息的附加信息。

我們的窗口過程處理三條消息:WM_LBUTTONDOWN,WM_KEYDOWN和WM_DESTROY消息。當用戶單擊窗口客戶區域上的鼠標左鍵時,會發送WM_LBUTTONDOWN消息。 當按下一個鍵時,WM_KEYDOWN消息被髮送到焦點窗口。 WM_DESTROY消息在窗口被銷燬時發送。

我們的代碼非常簡單; 當我們收到WM_LBUTTONDOWN消息時,我們會顯示一個消息框,輸出“Hello,World”:

case WM_LBUTTONDOWN:
    MessageBox(0, L“Hello, World“, L“Hello“, MB_OK);
    return 0;

當我們的窗口得到WM_KEYDOWN消息時,我們測試Escape鍵是否被按下,如果是,我們使用DestroyWindow函數銷燬主應用程序窗口。 傳遞給窗口過程的wParam指定了被按下的特定鍵的虛擬鍵碼。 將虛擬鍵碼看作特定鍵的標識符。 Windows頭文件有一個虛擬鍵代碼常量列表,我們可以用它來測試特定的鍵; 例如,爲了測試是否按下了轉義鍵,我們使用虛擬鍵碼常量VK_ESCAPE。

case WM_KEYDOWN:
    if(wParam == VK_ESCAPE)
        DestroyWindow(ghMainWnd);
return 0;

請記住,wParam和lParam參數用於指定有關特定消息的額外信息。對於WM_KEYDOWN消息,wParam指定被按下的特定鍵的虛擬鍵碼。 MSDN庫將爲每個Windows消息指定wParam和lParam參數的信息。

當我們的窗口被破壞時,我們用PostQuitMessage函數(它終止消息循環)發佈WM_QUIT消息:

case WM_DESTROY:
    PostQuitMessage(0);
    return 0;

在我們的窗口過程結束時,我們調用另一個名爲DefWindowProc的函數。該功能是默認的窗口過程。 在我們的基本Windows應用程序中,我們只處理三條消息; 我們使用DefWindowProc中指定的默認行爲來處理所有其他我們收到的消息,但不一定需要處理自己。 例如,基本Windows應用程序可以最小化,最大化,調整大小和關閉。 該功能通過默認的窗口過程提供給我們,因爲我們沒有處理消息來執行此功能。

3.7 MessageBox

還有一個我們尚未涉及的最後一個API函數,那就是MessageBox函數。 此功能是向用戶提供信息並獲得一些快速輸入的非常方便的方式。 對MessageBox函數的聲明如下所示:

int MessageBox(
    HWND hWnd, // Handle of owner window, may specify null.
    LPCTSTR lpText, // Text to put in the message box.
    LPCTSTR lpCaption,// Text to put for the title of the message box.
    UINT uType // Style of the message box.
);

MessageBox函數的返回值取決於消息框的類型。有關可能的返回值和樣式的列表,請參閱MSDN庫; 一種可能的風格是“是/否”消息框; 見圖A.5。


圖A.5. Yes/No message box.

4 一個更好的信息循環

與傳統的Windows應用程序(例如辦公室類型應用程序和Web瀏覽器)相比,遊戲是非常不同的應用程序。通常,遊戲並不等待消息,而是不斷更新。 這提出了一個問題,因爲如果消息隊列中沒有消息,則函數GetMessage將該線程休眠並等待消息。 對於一款遊戲,我們不希望這種行爲; 如果沒有Windows消息需要處理,那麼我們想運行我們自己的遊戲代碼。 解決方法是使用PeekMessage函數而不是GetMessage。 如果沒有消息,則PeekFunction消息立即返回。 我們的新消息循環如下所示:

int Run()
{
    MSG msg = {0};
    while(msg.message != WM_QUIT)
    {
        //如果有窗口消息,則處理它們。
        if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }//否則,做動畫/遊戲的東西。
        else
        {
        }
    }
    return (int)msg.wParam;
}

在我們實例化msg之後,我們進入了一個無限循環。我們首先調用API函數PeekMessage,它檢查消息隊列中的消息。 有關參數說明,請參閱MSDN。 如果有消息它會返回true,我們處理消息。 如果沒有消息,PeekMessage返回false,我們執行我們自己特定的遊戲代碼。

5總結

1.要使用Direct3D,我們必須創建一個Windows應用程序,該應用程序具有一個可以渲染3D場景的主窗口。此外,對於遊戲,我們創建一個特殊的消息循環來檢查消息。如果有消息,它會處理它們;否則,它會執行我們的遊戲邏輯。

2.多個Windows應用程序可以同時運行,因此Windows必須管理它們之間的資源並將消息引導到它們所針對的應用程序。當發生針對該應用程序的事件(按鍵,鼠標點擊,計時器等)時,將消息發送到應用程序的消息隊列。

3.每個Windows應用程序都有一個消息隊列,其中存儲應用程序接收的消息。應用程序的消息循環會不斷地檢查隊列中的消息,並將它們分派到其預期的窗口過程。請注意,一個應用程序可以有多個窗口。

4.窗口過程是一個特殊的回調函數,當我們的應用程序中的一個窗口收到一條消息時,我們實現Windows的調用。在窗口過程中,我們編寫了當我們的應用程序中的窗口收到特定消息時要執行的代碼。我們沒有特別處理的消息被轉發到默認窗口過程以進行默認處理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章