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

2023-05-11,,

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

1.编写入口函数WinMain

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

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

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

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

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

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

 #define SW_HIDE  0
#define SW_SHOWNORMAL 1
#define SW_NORMAL 1
#define SW_SHOWMINIMIZED 2
#define SW_SHOWMAXIMIZED 3
#define SW_MAXIMIZE 3
#define SW_SHOWNOACTIVATE 4
#define SW_SHOW 5
#define SW_MINIMIZE 6
#define SW_SHOWMINNOACTIVE 7
#define SW_SHOWNA 8
#define SW_RESTORE 9
#define SW_SHOWDEFAULT 10
#define SW_FORCEMINIMIZE 11
#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指定了函数的调用方式,即如何处理堆栈上的函数参数,其定义如下:

 #define WINAPI __stdcall

2.填充并注册WNDCLASS

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

 typedef struct tagWNDCLASS
{
UINT style;
WNDPROC lpfnWndProc;
INT cbClsExtra;
INT cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
}WNDCLASS, *PWNDCLASS, NEAR *NPWNDCLASS, FAR *LPWNDCLASS;

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

 #define CS_VREDRAW 0x0001
#define CS_HREDRAW 0x0002
#define CS_DBLCLKS 0x0008
#define CS_OWNDC 0x0020
#define CS_CLASSDC 0x0040
#define CS_PARENTDC 0x0080
#define CS_NOCLOSE 0x0200
#define CS_SAVEBITS 0x0800
#define CS_BYTEALIGNCLIENT 0x1000
#define CS_BYTEALIGNWINDOW 0x2000
#define CS_GLOBALCLASS 0x4000 #define CS_IME 0x00010000
#if(_WIN32_WINNT >= 0x0501)
#define CS_DROPSHADOW 0x00020000
#endif /* _WIN32_WINNT >= 0x0501 */

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

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

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

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

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

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

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

lpszMenuName:指定窗口菜单。

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

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

 ATOM WINAPI RegisterClass(_In_ CONST WNDCLASS *lpWndClass);

3.创建并显示窗口

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

 HWND WINAPI CreateWindow(
_In_opt_ LPCTSTR lpClassName,
_In_opt_ LPCTSTR lpWindowName,
_In_ DWORD dwStyle,
_In_ INT X,
_In_ INT Y,
_In_ INT nWidth,
_In_ INT nHeight,
_In_opt_ HWND hWndParent,
_In_opt_ HMENU hMenu,
_In_opt_ HANDLE hInstance,
_In_opt_ LPVOID lpParam);

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

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

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

 #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函数,其原型如下:

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

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

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

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

 BOOL WINAPI UpdateWindow(_In_ HWND hWnd);

4.消息循环

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

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

 typedef struct tagMSG
{
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
#ifdef _MAC
DWORD lPrivate;
#endif
}MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

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

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

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

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

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

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

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

 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将消息分发给特定的消息处理函数。

消息循环示例:

 INT run()
{
MSG msg = { }; BOOL bRet = ;
while ((bRet = GetMessage(&msg, NULL, NULL, NULL)))
{
if (bRet != -)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} return static_cast<INT>(msg.wParam);
}

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

改进后的消息循环示例:

 INT run()
{
MSG msg = { }; while (msg.message != WM_QUIT)
{ if (PeekMessage(&msg, , , , PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} return static_cast<INT>(msg.wParam);
}

5.消息处理函数

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

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

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

 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消息来告诉程序终止消息循环的运行。

消息处理函数示例:

 LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_DESTROY:
PostQuitMessage(NULL);
return ; default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}

Direct3D 10学习笔记(四)——Windows编程的相关教程结束。

《Direct3D 10学习笔记(四)——Windows编程.doc》

下载本文的Word格式文档,以方便收藏与打印。