DirectX3D程序需要将3D场景绘制到一个windows窗口的客户区中,因此复习一下简单的Win32GUI编程是有必要的,对于DirectX3D编程,这些也就足够了
Windows应用程序采用事件驱动模型,windows会将用户的行为作为事件发送给应用程序,应用程序启动时就维护了一个优先级队列,用于储存windows发来的事件消息,应用程序循环检查消息队列,取出最前端的消息,发送给窗口处理函数进行处理,这就是消息循环。
#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消息循环
如之前所述,WinMain比较简单,只处理了两件事
InitWindowsApp做了三件事
注册窗口类
创建窗口
显示窗口
run实现了消息循环
GetMessage获取消息,当获取到WM_QUIT时返回0,退出消息循环
WndProc是我们定义的窗口处理函数,用于处理run发送来的消息
改进的消息循环
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.