在使用vs2019创建新项目的时候,我们选择Windows桌面向导。之后,选择空项目即可。
主函数入口
在桌面程序里,我们的主函数入口不在是main函数了,而是WinMain函数或者wWinMain函数。如下所示:
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE prevInstance, _In_ LPWSTR cmdLine, _In_ int cmdShow)
{
return 0;
}
这两个函数的不同之处在于第三个参数cmdLine,第一个使用ANSI编码,而第二个使用Unicode编码。所以,这里我们主要使用后者。
窗口初始化
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE prevInstance, _In_ LPWSTR cmdLine, _In_ int cmdShow) {
WNDCLASSEX wndClass = { 0 };
wndClass.cbSize = sizeof(WNDCLASSEX);
wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = WndProc;
wndClass.hInstance = hInstance;
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = L"Dx11BookWindowClass";
if (!RegisterClassEx(&wndClass))
return -1;
HWND hwnd = CreateWindowW(L"Dx11BookWindowClass", L"Blank Win32 Window",
WS_OVERLAPPEDWINDOW, 0, 0, 640, 480, NULL, NULL, hInstance, NULL);
if (!hwnd) return -1;
ShowWindow(hwnd, cmdShow);
return 0;
}
窗口类由WNDCLASSEX所定义,包含窗口的各种属性。
创建完WNDCLASSEX结构后,调用函数RegisterClassEx()来注册窗口类。返回值为0,则表示注册失败。
接下来调用函数CreateWindowW来创建窗口。
如果创建成功,调用ShowWindow显示窗口。
注意GUI程序是事件驱动类型的程序,意思是当一个事件发生后,程序接收到它的通知,采取一些行动来响应。该过程一直运行,直到退出事件发生。但是,在视频游戏中,应用程序是实时的,意味着无论一些事件是否发生,都不会阻止程序在其生命期间执行许多任务。如果游戏控制器上用户按下按钮,它会被游戏循环中的更新阶段所检测到,游戏再来响应该事件。如果没有事件发生,游戏也一直不断地渲染当前游戏状态,执行逻辑更新,查找并响应网络数据,播放声音,等等。
程序循环
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE prevInstance, _In_ LPWSTR cmdLine, _In_ int cmdShow) {
// ...
MSG msg = { 0 };
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
} else {
// Update
// Draw
}
}
return static_cast<int>(msg.wParam);
}
程序循环就是一个无限的循环,直到用户跳出该循环为止。由接收WM_QUIT事件来发生结束指令。
MSG是一个用于持有来自于操作系统中window消息的结构,它用于程序响应这些消息。
PeeKMessage函数取得相关窗口的消息。
PeeKMessage是非阻塞函数;当系统无消息时,返回FALSE,继续执行后续的代码。
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE prevInstance, _In_ LPWSTR cmdLine, _In_ int cmdShow) {
WNDCLASSEX wndClass = { 0 };
wndClass.cbSize = sizeof(WNDCLASSEX);
wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.style = CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = WndProc;
wndClass.hInstance = hInstance;
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = L"Dx11BookWindowClass";
if (!RegisterClassEx(&wndClass))
return -1;
HWND hwnd = CreateWindowW(L"Dx11BookWindowClass", L"Blank Win32 Window",
WS_OVERLAPPEDWINDOW, 0, 0, 640, 480,
NULL, NULL, hInstance, NULL);
if (!hwnd) return -1;
ShowWindow(hwnd, cmdShow);
MSG msg = { 0 };
while (msg.message != WM_QUIT) {
if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return static_cast<int>(msg.wParam);
}
窗口过程
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
PAINTSTRUCT ps;
HDC hDC;
switch (message) {
case WM_PAINT:
hDC = BeginPaint(hwnd, &ps);
EndPaint(hwnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}

DefWindowProc函数只被那些我们没有自定义响应代码的消息所调用。