Windows 编程学习笔记 01
0x01 准备工作
VS2022 & C++
新建空项目并设置 项目属性->链接器->系统->子系统 为 窗口
遇到不确定用法的函数,可以选中并按 F1
0x02 Windows 常见的数据类型
UINT
无符号32位整型DTORD
32位整数PDWORD
32位整数类型指针。BOOL
布尔类型SHORT
带符号16位整数。LRESULT
32位函数返回值TPARAM
32位的消息参数。LPARAM
32位的消息参数。HANDLE
是 Windows 编程中一个特别重要的概念,在计算机中翻译为句柄。用于标示操作系统中的某个对象。HANDLE
通用句柄HWND
窗口句柄HINSTANCE
实例句柄LPSTR
字符串指针
0x03 一个简单的 HelloWorld
1 |
|
0x04 Windows 字符串
C++ 支持两种字符串,ANSI 编码(使用””包裹)和 Unicode 编码(使用L””包裹)
- 普通字符串类型 CHAR,即
char
,多字节字符集类型 - 宽字符串类型 WCHAR,即
wchar_t
,Unicode 类型,输出使用%ls
- 通用字符串类型 TCHAR,类型未知,由环境决定,需要引入
tchar.h
头文件。环境设置可见 项目属性->高级->字符集
微软将其通过条件编译进行统一(通过 _UNICODE 和 UNICODE 宏 ),于是有了_T("")
这样的字符串
常用字符串处理函数
分三种系列:str
, wcs
, _tcs
- 长度计算:strlen, wcslen, _tcslen
- 字符串转数字:
- A 版本:atoi, strtol
- W 版本:_wtoi, wcstol
- T 版本:_ttoi, _tcstol
- 数字到字符串:
- A 版本:_itoa_s, itoa(不安全)
- W 版本:_itow_s
- T 版本:_itot_s
两者相互转换(建议定义一个宏):
- W 到 A 的转换:WideCharToMultiByte()
- A 到 W 的转换:MultiByteToWideChar()
0x05 第一个窗口程序
写一个窗口程序需要四步:创建窗口类,注册窗口类,创建窗口实例,显示窗口
首先使用
WNDCLASSW
创建一个窗口类,关于它的参数:1
2
3
4
5
6
7
8
9
10
11
12typedef struct tagWNDCLASSW {
UINT style; // 样式
WNDPROC lpfnWndProc; // 回调函数 *这项必须有
int cbClsExtra; // 0
int cbWndExtra; // 0
HINSTANCE hInstance; // 程序的实例句柄
HICON hIcon; // 程序图标
HCURSOR hCursor; // 光标
HBRUSH hbrBackground; // 背景画刷
LPCWSTR lpszMenuName; // 菜单名称
LPCWSTR lpszClassName; // 程序类名 *这项必须有
} WNDCLASSW, *PWNDCLASSW, *NPWNDCLASSW, *LPWNDCLASSW;于是写作:
1
2
3
4// 1.创建一个窗口类
WNDCLASSW myclass = { 0 };
myclass.lpszClassName = L"windowClassTry"; // 定义类名
myclass.lpfnWndProc = nullptr; // 回调函数,先设置为无,之后修改当然,这里还有其他版本的创建函数,类似操作。
注册窗口类:一个类名只能注册一次
1
RegisterClassW(&myclass);
使用 CreateWindowW 创建窗口实例,关于它的参数:
1
2
3
4
5
6
7
8
9
10
11
12
13HWND CreateWindowW(
[in, optional] lpClassName, // 程序类名
[in, optional] lpWindowName, // 窗口名称
[in] dwStyle, // 窗口样式,例如WS_OVERLAPPEDWINDOW,可按F12快速查看
[in] x, // 坐标,可以使用 CW_USEDEFAULT 用户默认
[in] y, // x使用默认后可设置为0
[in] nWidth, // 宽,可设置默认 CW_USEDEFAULT
[in] nHeight, // 使用默认后可设置为0
[in, optional] hWndParent, // 父窗口句柄,没有则为NULL
[in, optional] hMenu,
[in, optional] hInstance,
[in, optional] lpParam // 回调参数,没有则可设置为0
);于是写作
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 3.创建窗口实例
HWND hwindow = CreateWindowW(
myclass.lpszClassName,
L"窗口名称",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
0,
CW_USEDEFAULT,
0,
NULL,
NULL,
hInstance,
0
);使用 ShowWindow 显示窗口
它只有两个参数,窗口句柄和显示样式(SW_开头,F12可查),这里写作
1
2// 4.显示窗口
ShowWindow(hwindow, SW_SHOWNORMAL);
编译运行,程序发生异常,原因是原本创建窗口类时的回调函数使用了空指针,需要进行修改。对 WNDCLASSW 按 F1
可查找到 lpfnWndProc 必须使用 CallWindowProc 函数调用窗口过程。 有关详细信息,请参阅 WindowProc。于是找到
1 | LRESULT Wndproc( |
直接将函数内容设置为 return 0
,完整代码如下:
1 | LRESULT Wndproc( |
此时程序没有发生异常,但刚运行便结束。关于如何解决这个问题,需要了解 Windows 的消息机制。
0x06 消息机制初步
Windows 系统是基于消息的操作系统。每一个窗口都在不停的处理消息,所有的操作都是接收到消息之后,进行处理的结果。围绕着消息的处理,产生了获取消息的消息泵机制也叫消息循环,以及处理消息的窗口回调函数机制。
Windows 下产生消息的时机共有以下四种:
- 用户主动产生的消息
- Windows 系统本身产生的消息:
- 应用程序本身产生的消息
- 其他应用程序产生的消息。
消息的发送与接收
Windows 系统有一个系统消息队列,每个 GUI 程序也有自己的消息队列,系统消息队列负责将消息发送给不同程序的消息队列。
GetMessage:从消息队列中取出消息,关于参数:
1
2
3
4
5
6GetMessageW(
_Out_ LPMSG lpMsg, // MSG 类型的消息变量
_In_opt_ HWND hWnd,// 窗口句柄
_In_ UINT wMsgFilterMin, // 获取消息的范围
_In_ UINT wMsgFilterMax);// 获取消息的范围
// 后面两个可以都设置为0来获取所有消息DispatchMessage:将消息发送给处理消息的回调函数
了解到以上知识,可以增加如下消息机制:
1
2
3
4
5
6// 5.获取消息
MSG msg = { 0 };
while (GetMessageW(&msg,0,0,0)){
DispatchMessageW(&msg);// 分发消息
}
// 第二个参数使用 0 表示处理全部程序的消息,以免无法发送最后的程序退出信号对应地,将原来的回调函数更改为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21LRESULT Wndproc(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
)
{
switch (uMsg)
{
case WM_CREATE:
MessageBoxW(hwnd,L"窗口已创建",L"窗口标题",MB_OK);
break;
case WM_CLOSE:
MessageBoxW(hwnd, L"窗口已关闭", L"窗口标题", MB_OK);
DestroyWindow(hwnd); // 销毁窗口
PostQuitMessage(0); // 发送程序退出消息,这里和java的swt类似
default:
break;
}
return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
最后我们得到一个可以完整的窗口程序:
1 |
|
注:写最后部分代码时遇到了一点点小坑,代码注释中可见一斑。