Direct3D 10学习笔记(四)——Windows编程

Posted on 2015-02-10 15:04  太息花色  阅读(592)  评论(0)    收藏  举报

本篇将简单整理基本的Windows应用程序的实现,并作为创建Direct3D 10应用程序的铺垫。具体内容参照《 Introduction to 3D Game Programming with DirectX 10》(中文版有汤毅翻译的电子书《DirectX 10 3D游戏编程入门》)中的附录A。

 

1.编写入口函数WinMain

 

Windows编程中的WinMain函数与普通C++编程的main函数作用相同,指定了程序开始的执行点。其函数原型如下:

 

1 INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nShowCmd)

 

hInstance:唯一标志当前运行中的实例的句柄,同一Windows应用程序可以有多个实例并行运行,用于引用某个特定实例以作区分。

 

hPrevInstance:Win32环境下该参数总为NULL,即Win32编程不再使用。

 

lpCmdLine:程序启动时传入命令行参数的字符串。

 

nShowCmd:指定程序窗口的显示方式。其可用的值用宏来枚举,如下:

 1 #define SW_HIDE  0
 2 #define SW_SHOWNORMAL 1
 3 #define SW_NORMAL 1
 4 #define SW_SHOWMINIMIZED 2
 5 #define SW_SHOWMAXIMIZED 3
 6 #define SW_MAXIMIZE 3
 7 #define SW_SHOWNOACTIVATE 4
 8 #define SW_SHOW 5
 9 #define SW_MINIMIZE 6
10 #define SW_SHOWMINNOACTIVE 7
11 #define SW_SHOWNA 8
12 #define SW_RESTORE 9
13 #define SW_SHOWDEFAULT 10
14 #define SW_FORCEMINIMIZE 11
15 #define SW_MAX 11

 

SW_HIDE:隐藏窗口并将其活动状态传递给其他窗口。

SW_NORMAL:激活并显示一个窗口。如果窗口被最小化或最大化,系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口的时候应该指定此标志。

SW_SHOWMINIMIZED:激活窗口并将其最小化。
SW_SHOWMAXIMIZED:激活窗口并将其最大化。
SW_MAXIMIZE:最大化指定的窗口。
SW_SHOWNOACTIVATE:以窗口最近一次的大小和状态显示窗口。激活窗口仍然维持激活状态。
SW_SHOW:在窗口原来的位置以原来的尺寸激活和显示窗口。
SW_MINIMIZE:最小化指定的窗口并且激活在Z序中的下一个顶层窗口。
SW_SHOWMINNOACTIVE:将窗口显示为图标。当前激活窗口仍维持激活状态。
SW_SHOWNA:以窗口原来的状态显示窗口。激活窗口仍然维持激活状态。
SW_RESTORE:激活并显示窗口。如果窗口最小化或最大化,则系统将窗口恢复到原来的尺寸和位置。在恢复最小化窗口时,应用程序应该指定这个标志。
SW_SHOWDEFAULT:依据在STARTUPINFO结构中指定的SW_FLAG标志设定显示状态,STARTUPINFO结构是由启动应用程序的程序传递给CreateProcess函数的。
SW_FORCEMINIMIZE:在WindowNT5.0中最小化窗口,即使拥有窗口的线程被挂起也会最小化。在从其他线程最小化窗口时才使用这个参数。

 

如果WinMain调用成功,将返回WM_QUIT中的wParam成员。如果函数在进入消息循环前就已退出,则返回0。其中标识符WINAPI指定了函数的调用方式,即如何处理堆栈上的函数参数,其定义如下:

 

1 #define WINAPI __stdcall

 

2.填充并注册WNDCLASS

 

要初始化一个窗口,首先要填充一个WNDCLASS结构体,以描述窗口的基本属性。WNDCLASS定义如下:

 1 typedef struct tagWNDCLASS
 2 {
 3     UINT style;
 4     WNDPROC lpfnWndProc;
 5     INT cbClsExtra;
 6     INT cbWndExtra;
 7     HINSTANCE hInstance;
 8     HICON hIcon;
 9     HCURSOR hCursor;
10     HBRUSH hbrBackground;
11     LPCTSTR lpszMenuName;
12     LPCTSTR lpszClassName;
13 }WNDCLASS, *PWNDCLASS, NEAR *NPWNDCLASS, FAR *LPWNDCLASS;

 

style:指定窗口样式。其可用枚举如下:

 1 #define CS_VREDRAW 0x0001
 2 #define CS_HREDRAW 0x0002
 3 #define CS_DBLCLKS 0x0008
 4 #define CS_OWNDC 0x0020
 5 #define CS_CLASSDC 0x0040
 6 #define CS_PARENTDC 0x0080
 7 #define CS_NOCLOSE 0x0200
 8 #define CS_SAVEBITS 0x0800
 9 #define CS_BYTEALIGNCLIENT 0x1000
10 #define CS_BYTEALIGNWINDOW 0x2000
11 #define CS_GLOBALCLASS 0x4000
12 
13 #define CS_IME 0x00010000
14 #if(_WIN32_WINNT >= 0x0501)
15 #define CS_DROPSHADOW 0x00020000
16 #endif /* _WIN32_WINNT >= 0x0501 */

 

lpfnWndProc:指向消息处理函数的指针。

 

cbClsExtra、cbWndExtra:用于存储附加数据,一般不会用到,设置为NULL。

 

hInstance:应用程序实例句柄。与WinMain函数的第一个参数相同。

 

hIcon:指定显示窗口标题栏上的图标。

 

hCursor:指定鼠标位于窗口客户区上显示的光标。

 

hbrBackground:指定客户窗口区的背景色。可以使用Win32函数GetStockObject返回一个内置画刷句柄,其用法示例如下:  

1 wc.hbrBackground = static_cast<HBRUSH>(GetStockObject(NULL_BRUSH));

 

lpszMenuName:指定窗口菜单。

 

lpszClassName:指定窗口类名称,即为该WNDCLASS实例起名。若需使用相应WNDCLASS实例,可以通过这个名称来引用。

 

填充完成WNDCLASS后,需要使用RegisterClass函数注册WNDCLASS到系统中,调用失败返回NULL。

1 ATOM WINAPI RegisterClass(_In_ CONST WNDCLASS *lpWndClass);

 

3.创建并显示窗口

 

创建窗口使用CreateWindow函数,其原型如下:

 1 HWND WINAPI CreateWindow(
 2     _In_opt_ LPCTSTR lpClassName,
 3     _In_opt_ LPCTSTR lpWindowName,
 4     _In_ DWORD dwStyle,
 5     _In_ INT X,
 6     _In_ INT Y,
 7     _In_ INT nWidth,
 8     _In_ INT nHeight,
 9     _In_opt_ HWND hWndParent,
10     _In_opt_ HMENU hMenu,
11     _In_opt_ HANDLE hInstance,
12     _In_opt_ LPVOID lpParam);

 

lpClassName:已注册的WNDCLASS实例名称。

 

lpWindowName:窗口名称,即显示在窗口标题栏上的字符串名称。

 

dwStyle:定义窗口样式。如WS_OVERLAPPEDWINDOW是多个描述窗口特征的标志值的组合:

 

1 #define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)

 

x:窗口左上角位于屏幕上的x坐标。若指定为CW_USEDEFAULT,系统会为窗口选择一个适当的默认坐标。

 

y:窗口左上角位于屏幕上的y坐标。若指定为CW_USEDEFAULT,系统会为窗口选择一个适当的默认坐标。

 

nWidth:窗口宽度,单位为像素。若指定为CW_USEDEFAULT,系统会为窗口选择一个适当的默认宽度。

 

nHeight:窗口高度,单位为像素。若指定为CW_USEDEFAULT,系统会为窗口选择一个适当的默认高度。

 

hWndParent:当前窗口的父窗口句柄。若没有父窗口则设置为NULL。

 

hMenu:菜单句柄。若窗口没有菜单,则设置为NULL。

 

hInstance:与窗口关联的应用程序实例句柄。

 

lpParam:传递给WM_CREATE消息处理函数的用户自定义数据指针。当创建窗口时,操作系统会向窗口发送一个WM_CREATE消息。该消息会在CreateWindow方法返回前发出。如果希望创建窗口时执行某些操作,则需要处理WM_CREATE消息。

 

CreateWindow创建窗口成功后返回该窗口的句柄。创建失败则返回NULL。需要记录下该窗口的句柄,以便将其传入给API函数,明确操作的窗口对象。

 

控制窗口的显示需要使用ShowWindow函数,其原型如下:

 

1 BOOL WINAPI ShowWindow(_In_ HWND hWnd, _In_ int nCmdShow);

 

 

hWnd:指定想要显示的窗口的句柄。

 

nCmdShow:指定窗口的初始显示状态。该值可以设为WinMain的第四个参数。

 

初次显示窗口,必须调用UpdateWindow方法,对窗口进行一次手动刷新,其原型如下:

1 BOOL WINAPI UpdateWindow(_In_ HWND hWnd);

 

4.消息循环

 

完成窗口的初始化工作后,需要编写程序的核心部分——消息循环。

 

首先声明一个MSG结构体类型的变量来表示Windows消息,其原型如下:

 1 typedef struct tagMSG
 2 {
 3     HWND hwnd;
 4     UINT message;
 5     WPARAM wParam;
 6     LPARAM lParam;
 7     DWORD time;
 8     POINT pt;
 9 #ifdef _MAC
10     DWORD lPrivate;
11 #endif
12 }MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

 

hwnd:指定接收消息的窗口句柄。

 

message:用于标识特定消息预定义常量。

 

wParam:与消息相关的附加值,取值依具体消息而定。

 

lParam:与消息相关的附加值,取值依具体消息而定。

 

time:消息被送入队列的时间。

 

Pt:当消息送入队列时,鼠标在屏幕上的坐标。

 

随后进入消息循环,使用GetMessage函数从队列中检索消息,并使用消息的具体内容填充MSG变量,其原型如下:

1 BOOL GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax)

 

lpMsg:指向MSG变量的指针。

 

hWnd、wMsgFilterMin、wMsgFilterMax:均设置为NULL。

 

当出错时,GetMessage返回-1;当收到WM_QUIT消息时,GetMessage返回0;当返回其他值时,需要调用TranslateMessage和DispatchMessage函数。这两个函数均接收一个指向MSG变量的指针。TranslateMessage将虚拟键盘码转换为字符信息,而DispatchMessage将消息分发给特定的消息处理函数。

 

消息循环示例:

 1 INT run()
 2 {
 3     MSG msg = { 0 };
 4 
 5     BOOL bRet = 1;
 6     while ((bRet = GetMessage(&msg, NULL, NULL, NULL)))
 7     {
 8         if (bRet != -1)
 9         {
10             TranslateMessage(&msg);
11             DispatchMessage(&msg);
12         }
13     }
14 
15     return static_cast<INT>(msg.wParam);
16 }

 

在游戏中由于会主动重绘界面,而不是坐等消息到来。传统消息循环中GetMessage会导致当队列没有消息时让线程进入休眠状态。故在游戏中使用PeekMessage取代GetMessage,该函数能在没有消息时直接返回,不会阻塞线程。

 

改进后的消息循环示例:

 1 INT run()
 2 {
 3     MSG msg = { 0 };
 4 
 5     while (msg.message != WM_QUIT)
 6     {
 7 
 8         if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
 9         {
10             TranslateMessage(&msg);
11             DispatchMessage(&msg);
12         }
13     }
14 
15     return static_cast<INT>(msg.wParam);
16 }            

 

5.消息处理函数

 

消息处理函数也称为窗口过程函数。它用来对窗口接收到的消息做出响应,执行对应的指令。

消息处理函数由自己定义,它需是一个回调函数,使用CALLBACK标识符,系统将在程序的代码空间外调用这个函数,即不需要直接调用该函数。当窗口需要处理一个消息时,系统会自动调用该函数。

消息处理函数的声明可以如下:

1 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

 

hwnd:接收消息的窗口句柄。

 

msg:标识特定消息的预定义常量。用前缀"WM"表示窗口消息,可取的值有上百个,具体参见MSDN。

 

wParam:与消息相关的附加值,取值依具体消息而定。

 

lParam:与消息相关的附加值,取值依具体消息而定。

 

DefWindowProc函数是默认的消息处理函数,那些未被处理的消息就会以DefWindowProc中定义的默认行为来处理,即不需要亲自处理所有消息。

 

可以用多分支语句来处理消息,如接收到由窗口销毁函数DestroyWindow发送的WM_DESTROY消息时,调用PostQuitMessage函数再发送一个WM_QUIT消息来告诉程序终止消息循环的运行。

 

消息处理函数示例:

 1 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
 2 {
 3     switch (msg)
 4     {
 5         case WM_DESTROY:
 6             PostQuitMessage(NULL);
 7             return 0;
 8 
 9         default:
10             return DefWindowProc(hwnd, msg, wParam, lParam);
11 }