来学学 windows 的基础知识,在 C++ 中的实现。

字符类型

在 C++ 中,两个标准的字符类型是 charwchar_t 类型,分别是代表 ASCII 字符和 UNICODE 字符。windows 对它进行了二次定义,变成了 CHARWCHAR 类型。并且为了兼容环境,还出现了 TCHAR,它会跟着项目环境选择 CHAR 还是 WCHAR

有个函数可以把单字节转为宽字节:MultiByteToWideChar

1
2
3
4
CHAR* s = (CHAR*)"xia0ji233";
WCHAR buffer[50];
MultiByteToWideChar(CP_ACP, NULL, s, -1, buffer, 50);
printf("%S", buffer);

这里格式化字符串 %S 不同于 %s%S 是宽字符,如果使用 %s 则会因为 ASCII 字符转 unicode 多了一个 00 字节而被截断。

还有个函数可以转回去。

1
2
3
4
WCHAR* ws = (WCHAR*)L"xia0ji233";
CHAR buffer[50];
WideCharToMultiByte(CP_ACP, NULL, ws, -1, buffer, 50, NULL, NULL);
printf("%s\n", buffer);

控制台程序的入口是 int main(int argc,char *argv[],char *env[]),而 windows 程序的入口是 WinMain(),参数介绍如下:

  • hInstance 实例句柄 指向 exe 模块
  • hPrevInstance 废弃了
  • lpCmdLine 命令行参数
  • nCmdShow程序显示模式,最大化显示,最小化

MFC 就是对这个函数进行的一个封装。

Windows 的程序是基于消息的。

MessageBox API介绍

先学第一个 API——MessageBox,作用是弹窗。

MessageBox 有四个参数:

  • hwnd:窗口句柄
  • lptext:内容
  • lpCaption:标题
  • uType:按钮类型

返回值会判断我们对这个窗口做了哪些处理,也是由一些宏实现的,这里也不想记流水账,就写一下 IDOK 返回值表示我们按了确定按钮。

MessageBox有很多版本,ASCII 版(MessageBoxA),宽字版(MessageBoxW),而 MessageBox 会对我们当前的环境判断而选择合适的版本。并且还推出了 Ex 版,主要是因为想把 MessageBox 的定义改一下,但是又要保持兼容性,因此出了这样的增强版,增强版多了一个参数就是语言类型。

windows程序开发入门

就跟着做一下。

创建空项目,把链接器→系统中的子系统改为 windows,随后就可以开始 windows 编程了。

之后我们要以 WinMain 函数为原型,并实现它,windows 默认里面会有一个死的消息循环,我们不需要使用某些暂停的手段,我们也不能使用标准输入输出函数打印内容。

创建窗口有以下几个过程:

  1. 填充窗口类
  2. 创建窗口
  3. 显示窗口
  4. 更新窗口
  5. 建立消息循环
  6. 实现窗口过程函数

这里穿插一个小知识:类似的对结构体的第一个字段赋值 sizeof(type) 是为了防止结构体更新导致的不能向下兼容。

填充窗口类

写一个函数填充窗口结构,包括图标,鼠标指针,任务栏图标……,最后调用一个 RegisterClassExW 去根据这个结构注册窗口类。

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
ATOM MyRegisterClass(HINSTANCE hInstance) {
WNDCLASSEXW wcex;
//结构尺寸
wcex.cbSize = sizeof(WNDCLASSEXW);
//窗口的风格
wcex.style = CS_HREDRAW | CS_VREDRAW;
////Windows窗口的主过程(消息的处理函数)
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
//图标
wcex.hIcon = 0;//LoadIcon
//光标
wcex.hCursor = 0;//LoadCursor
//类背景画笔的句柄
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
//菜单的句柄
wcex.lpszMenuName = 0;
//指向类名称的句柄
wcex.lpszClassName = wszClassName;
//任务栏图标
wcex.hIconSm = 0;//LoadIcon
//注册窗口类
return RegisterClassEx(&wcex);
}

创建窗口

创建窗口,填一些参数,返回一个窗口句柄。

1
2
3
4
5
6
7
8
9
10
11
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd = CreateWindow(wszClassName, TITLE, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 500, 500,
NULL, NULL, hInstance, NULL);
if (!hWnd) {
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}

这里面还包含了显示窗口和更新窗口。

消息循环与实现处理函数

就是消息的翻译和分发。

1
2
3
4
5
6
MSG msg;
// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}

实现处理函数,这里主要对窗口绘制和退出做一个动作,其它的过程函数都是在这里实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{

}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

最终代码

最后的代码就是这样的:

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include<stdio.h>
#include<windows.h>
using namespace std;
const WCHAR* wszClassName = L"xia0ji233";
const WCHAR* TITLE = L"Create By xia0ji233";
LRESULT CALLBACK WndProc(HWND hWnd, UINT, WPARAM, LPARAM);
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
int WinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPSTR lpCmdLine,
_In_ int nShowCmd
) {
MyRegisterClass(hInstance);
if (InitInstance(hInstance, nShowCmd)) {
MSG msg;

// 主消息循环:
while (GetMessage(&msg, nullptr, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

}
ATOM MyRegisterClass(HINSTANCE hInstance) {
WNDCLASSEXW wcex;
//结构尺寸
wcex.cbSize = sizeof(WNDCLASSEXW);
//窗口的风格
wcex.style = CS_HREDRAW | CS_VREDRAW;
////Windows窗口的主过程(消息的处理函数)
wcex.lpfnWndProc = WndProc;
//跟在窗口类结构后面的附加字节数,给类预留的空间
wcex.cbClsExtra = 0;
//跟在窗口实例后面的附加字节数,给实例预留的空间
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
//图标
wcex.hIcon = 0;//LoadIcon
//光标
wcex.hCursor = 0;//LoadCursor
//类背景画笔的句柄
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
//菜单的句柄
wcex.lpszMenuName = 0;
//指向类名称的句柄
wcex.lpszClassName = wszClassName;
//任务栏图标
wcex.hIconSm = 0;//LoadIcon
//注册窗口类
return RegisterClassEx(&wcex);
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd = CreateWindow(wszClassName, TITLE, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, 500, 500,
NULL, NULL, hInstance, NULL);
if (!hWnd) {
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;

}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{

}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

直接运行,成功!