简单的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消息循环

  • 如之前所述,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.

posted @ 2014-09-20 21:05  current  阅读(620)  评论(0编辑  收藏  举报