跳转至

入门

一个简单的 Win32 程序

让我们先看看一个最简单的 Win32 程序的例子:

代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <Windows.h>

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));

        EndPaint(hwnd, &ps);
    }
    break;

    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

int Win32 wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PTSTR cmdLine, int cmdShow) {
    const TCHAR CLASS_NAME[] = TEXT("WindowClass");

    WNDCLASS wc = { };
    wc.lpfnWndProc = WindowProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;

    RegisterClass(&wc);

    HWND hwnd = CreateWindowEx(0, CLASS_NAME, TEXT("WindowName"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL);

    if (hwnd == NULL)
        return 0;
    ShowWindow(hwnd, cmdShow);

    MSG msg = { };
    while (GetMessage(&msg, NULL, 0, 0) > 0)
        TranslateMessage(&msg), DispatchMessage(&msg);
    return 0;
}

看起来非常复杂,让我们先拆解来看。

头文件

Win32 用到的绝大多数东西都在 Windows.hCommCtrl.h 这两个头文件里。

Warning

虽然 CommCtrl.h 里用到了很多 Windows.h 的东西,但是它并没有 #include <Windows.h>,因此在使用 CommCtrl.h 时,一定要先 #include <Windows.h>#include <CommCtrl.h>

主函数

1
2
3
4
5
6
int Win32 wWinMain(
HINSTANCE hInstance,        // 实例的句柄,这个数据类型及其实际存储的信息不用理解,只需要直接使用这个值
HINSTANCE hPrevInstance,    // 没有任何意义,始终为 0
PTSTR cmdLine,              // 为命令行参数,在初学阶段不会使用
int cmdShow                 // 为指示窗口状态(最小化、最大化、正常显示)的值,其具体值我们不用理解
)

不同于控制台程序,窗口程序以 wWinMain 为入口,其中「w」表示这个程序使用 Unicode 字符(宽字符 wchar_t),若没有这个「w」则使用 ANSI 字符(char)。Win32 修饰函数,具体含义不用理解。

创建窗口

注册窗口类

在 Win32 中,所有的组件都是窗口,每种窗口都有对应的窗口类(具体的解释见「窗口类」章节),对于主窗口,我们需要注册窗口类(可以理解为定义一个类)。

1
2
3
4
5
6
7
8
9
const TCHAR CLASS_NAME[] = TEXT("WindowClass"); // TCHAR 即为 wchar_t

WNDCLASS wc = { };              // 使用一个结构体存储窗口类的参数

wc.lpfnWndProc = WindowProc;    // 窗口过程函数(后文会讲)
wc.hInstance = hInstance;       // 实例的句柄
wc.lpszClassName = CLASS_NAME;  // 窗口类名称,以一个字符串为标识

RegisterClass(&wc);             // 注册窗口类

创建窗口

使用 CreateWindowEx 从来没有见过这么多参数的函数 创建窗口。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
HWND hwnd = CreateWindowEx(
0,                      // 扩展窗口样式,这里不使用扩展窗口,因此设置为 0
CLASS_NAME,             // 标识窗口类的字符串
TEXT("WindowName"),     // 窗口标题
WS_OVERLAPPEDWINDOW,    // 窗口样式,此处为一个重叠的窗口(带有标题栏和边框、图标、最小化、最大化、退出按钮,可调整大小的窗口)
CW_USEDEFAULT,          // 窗口的最左侧位置,以屏幕左上角为 0,这里使用默认值
CW_USEDEFAULT,          // 窗口的最上侧位置,以屏幕左上角为 0,这里使用默认值
CW_USEDEFAULT,          // 窗口的宽度,这里使用默认值
CW_USEDEFAULT,          // 窗口的高度,这里使用默认值
NULL,                   // 父窗口的句柄,因为主窗口没有父窗口,所以为 NULL
NULL,                   // 这里表示不使用菜单
hInstance,              // 实例的句柄
NULL                    // 在初学阶段不会使用,设置为 NULL
)

CreateWindowEx 返回值类型为 HWND,表示创建窗口的句柄(可以理解为一个指针)。如果创建失败,将返回 NULL,此时需要退出程序;否则显示窗口。

1
2
3
if (hwnd == NULL)
    return 0;
ShowWindow(hwnd, cmdShow);

消息处理

对 Win32 窗口的操作(比如点击按钮、按下键盘等)将以消息队列的形式发送给程序,程序需要使用消息循环来处理消息。

消息循环

1
2
3
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
    TranslateMessage(&msg), DispatchMessage(&msg);

消息以 MSG 类型存储,我们不需要知道这玩意到底是怎么存储的,只需要使用它处理消息。GetMessage 函数的四个参数含义如下:

1
2
3
4
5
GetMessage(
&msg,   // 将读取到的消息存储在这个对象中
NULL,   // 这里表示从所有窗口里检索消息
0, 0    // 这里表示不进行范围筛选,返回所有可用消息
)

对于一般的消息 GetMessage 返回一个大于 \(0\) 的值,但如果收到 WM_QUIT 消息,则返回 \(0\),此时应该退出消息循环,因此使用上面的 while 结构。

对于一条消息,我们只需要使用 TranslateMessageDispatchMessage 两个函数即可完成消息处理。同样,它们的实现方式我们不需要理解,只需要知道消息处理过程中会调用窗口过程

窗口过程

窗口过程是处理窗口消息的函数,在注册窗口类的时候需要作为参数传入(一般来说,为了更好看,会先声明,再在主函数后面定义,这里为了更好理解并没有选择这种方式)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);

        FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));

        EndPaint(hwnd, &ps);
    }
    break;

    default:
        return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

返回值 LRESULT 和函数的修饰符 CALLBACK 不用理解,只需要知道 LRESULT 大概就是个整型即可。WindowProc 的四个参数含义如下:

1
2
3
4
5
LRESULT CALLBACK WindowProc(
HWND hwnd,                      // 窗口的句柄
UINT uMsg,                      // 消息代码
WPARAM wParam, LPARAM lParam    // 消息参数,其含义取决于消息类型
)

消息代码一般使用 switch case 语句处理。WM_DESTROY 为销毁窗口的消息,对于此消息来说,wParamlParam 均没有用处。在处理此消息时,我们一般会使用 PostQuitMessage(0),这个语句会向消息队列中发送 WM_QUIT 消息,如上文介绍,它会结束消息循环(如果不这样处理,会出现窗口关闭而程序未结束的情况),其中 \(0\) 为退出代码(并不代表收到 WM_QUIT 消息时 GetMessage 的返回值),初学时不使用退出代码,直接设置为 \(0\)

WM_PAINT 为绘制窗口的消息,对于此消息来说,wParamlParam 均没有用处。这里的处理方式为使用背景色填充窗口,具体实现方式现在不需要理解。

这个窗口还可能出现其他的消息,对于那些不需要特殊处理的消息,我们不需要一一列出,只需要调用默认窗口过程 DefWindowProc,并将参数传入即可实现默认处理。