MFC---Windows消息机制
  5Z6Aj0LQwRjK 2023年11月02日 28 0


为什么学习MFC

如果你是在Windows平台上做GUI开发,MFC(微软基础类库)是一个很好的选择,毕竟Windows累积用户群庞大,市场接受程度高。
但是,学习MFC不仅仅要学习用MFC,还要学习MFC的框架设计思想。
很多公司在一些做了很久的项目上,往往都是有自己的类库、自己的框架,我们只需要在其基础上不断的完善和扩展。如果你不了解类库,你是根本无从下手。这也是我们要学习类库、框架设计的原因。

Windows消息机制

要想熟练掌握 Windows 应用程序的开发, 首先需要理解 Windows 平台下程序运行的内部机制。如果想要更好的学习掌握 MFC,必须要先了解Windows 程序的内部运行机制,为我们扫清学习路途中的第一个障碍,为进一步学习 MFC 程序打下基础。

基本概念解释

我们在编写标准C程序的时候,经常会调用各种库函数来辅助完成某些功能:初学者使用得最多的C库函数就是printf了,这些库函数是由你所使用的编译器厂商提供的。在Windows平台下,也有类似的函数可供调用:不同的是,这些函数是由Windows操作系统本身提供的。

SDK和API

SDK: 软件开发工具包(Software Development Kit),一般都是一些被软件工程师用于为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。
API函数: Windows操作系统提供给应用程序编程的接口(Application Programming Interface)。
Windows应用程序API函数是通过C语言实现的,所有主要的 Windows 函数都在 Windows.h 头文件中进行了声明。Windows 操作系统提供了 1000 多种 API函数。

窗口和句柄

窗口是 Windows 应用程序中一个非常重要的元素,一个 Windows 应用程序至少要有一个窗口,称为主窗口。
窗口是屏幕上的一块矩形区域,是 Windows 应用程序与用户进行交互的接口。利用窗口可以接收用户的输入、以及显示输出。
一个应用程序窗口通常都包含标题栏、菜单栏、系统菜单、最小化框、最大化框、 可调边框,有的还有滚动条。如下图:

MFC---Windows消息机制_windows

窗口可以分为客户区和非客户区, 如上图。 客户区是窗口的一部分, 应用程序通常在客户区中显示文字或者绘制图形。
标题栏、 菜单栏、 系统菜单、 最小化框和最大化框、 可调边框统称为窗口的非客户区, 它们由 Windows 系统来管理, 而应用程序则主要管理客户区的外观及操作。

窗口可以有一个父窗口, 有父窗口的窗口称为子窗口。除了上图所示类型的窗口外, 对话框和消息框也是一种窗口。 在对话框上通常还包含许多子窗口, 这些子窗口的形式有按钮、 单选按钮、 复选框、 组框、 文本编辑框等。

在 Windows 应用程序中, 窗口是通过窗口句柄( HWND) 来标识的。 我们要对某个窗口进行操作, 首先就要得到这个窗口的句柄。

句柄( HANDLE) 是 Windows 程序中一个重要的概念, 使用也非常频繁。 在 Windows 程序中, 有各种各样的资源( 窗口、 图标、光标,画刷等), 系统在创建这些资源时会为它们分配内存, 并返回标识这些资源的标识号, 即句柄。 在后面的内容中我们还会看到图标句柄( HICON)、 光标句柄( HCURSOR) 和画刷句柄( HBRUSH)。

消息与消息队列

Windows 程序设计是一种完全不同于传统的 DOS 方式的程序设计方法。它是一种事件驱动方式的程序设计模式,主要是基于消息的。每一个 Windows 应用程序开始执行后, 系统都会为该程序创建一个消息队列, 这个消息队列用来存放该程序创建的窗口的消息。

例如,当用户在窗口中画图的时候,按下鼠标左键,此时,操作系统会感知到这一事件,于是将这个事件包装成一个消息,投递到应用程序的消息队列中,等待应用程序的处理。

然后应用程序通过一个消息循环不断地从消息队列中取出消息,并进行响应。 在这个处理过程中,操作系统也会给应用程序“ 发送消息”。所谓“ 发送消息”,实际上是操作系统调用程序中一个专门负责处理消息的函数,这个函数称为窗口过程。

MFC---Windows消息机制_应用程序_02

WinMain函数

当Windows操作系统启动一个程序时,它调用的就是该程序的WinMain函数( 实际是由插入到可执行文件中的启动代码调用的)。 WinMain是Windows程序的入口点函数,与DOS程序的入口点函数main的作用相同,当WinMain 函数结束或返回时,Windows应用程序结束。

Windows 编程模型

一个完整的Win32程序(#include <windows.h>),该程序实现的功能是创建一个窗口,并在该窗口中响应键盘及鼠标消息,程序的实现步骤为:
1)WinMain函数的定义
2)创建一个窗口
3)进行消息循环
4)编写窗口过程函数

项目的创建

MFC---Windows消息机制_c++_03

MFC---Windows消息机制_Windows_04

MFC---Windows消息机制_windows_05


MFC---Windows消息机制_mfc_06


MFC---Windows消息机制_应用程序_07

WinMain函数的定义

int WINAPI WinMain(
	HINSTANCE hInstance,	//应用程序实例
	HINSTANCE hPrevInstance,	//上一个应用程序实例
	LPSTR lpCmdLine,		//命令行参数
	int nShowCmd);		//窗口显示的样式

WINAPI:是一个宏,它代表的是__stdcall(注意是两个下划线),表示的是参数传递的顺序:从右往左入栈,同时在函数返回前自动清空堆栈。

hInstance:表示该程序当前运行的实例的句柄,这是一个数值。当程序在Windows下运行时,它唯一标识运行中的实例(注意,只有运行中的程序实例, 才有实例句柄)。一个应用程序可以运行多个实例,每运行一个实例,系统都会给该实例分配一个句柄值,并通过hInstance参数传递给 WinMain 函数。

hPrevInstance:表示当前实例的前一个实例的句柄。在Win32环境下,这个参数总是NULL,即在Win32环境下,这个参数不再起作用。

lpCmdLine:是一个以空终止的字符串, 指定传递给应用程序的命令行参数,相当于C或C++中的main函数中的参数char *argv[]。

nShowCmd:表示一个窗口的显示,表示它是要最大化显示、最小化显示、正常大小显示还是隐藏显示。

创建一个窗口

创建一个完整的窗口,需要经过下面几个步骤:
a.设计一个窗口类
b.注册窗口类
c.创建窗口
d.显示及更新窗口

设计一个窗口类

一个完整的窗口具有许多特征, 包括光标(鼠标进入该窗口时的形状)、图标、背景色等。窗口的创建过程类似于汽车的制造过程。我们在生产一个型号的汽车之前, 首先要对该型号的汽车进行设计, 在图纸上画出汽车的结构图, 设计各个零部件, 同时还要给该型号的汽车取一个响亮的名字, 例如“宝马 x6”。
类似地, 在创建一个窗口前, 也必须对该类型的窗口进行设计, 指定窗口的特征。在Windows中,窗口的特征就是由WNDCLASS结构体来定义的,我们只需给WNDCLASS结构体对应的成员赋值,即可完成窗口类的设计。

WNDCLASS结构体的定义如下:

typedef struct _WNDCLASS{
	UINT        style;
	WNDPROC     lpfnWndProc;
	int         cbClsExtra;
	int         cbWndExtra;
	HINSTANCE   hInstance;
	HICON       hIcon;
	HCURSOR     hCursor;
	HBRUSH      hbrBackground;
	LPCWSTR     lpszMenuName;
	LPCWSTR     lpszClassName;
} WNDCLASS;

style:指定窗口的样式(风格),常用的样式如下:

MFC---Windows消息机制_c++_08


lpfnWndProc:指定一个窗口回调函数,是一个函数的指针。

当应用程序收到给某一窗口的消息时,就应该调用某一函数来处理这条消息。这一调用过程不用应用程序自己来实施,而由操作系统来完成,但是,回调函数本身的代码必须由应用程序自己完成。 对于一条消息,操作系统调用的是接受消息的窗口所属的类型中的lpfnWndProc成员指定的函数。每一种不同类型的窗口都有自己专用的回调函数,该函数就是通过lpfnWndProc成员指定的。

回调函数的定义形式如下:

LRESULT CALLBACK WindowProc(
	HWND hWnd,		//信息所属的窗口句柄
	UINT uMsg,		//消息类型
	WPARAM wParam,	//附加信息(如键盘哪个键按下)
	LPARAM lParam	//附加信息(如鼠标点击坐标)
	);

cbClsExtra:类的附加内存,通常数情况下为0。
cbWndExtra:窗口附加内存,通常情况下为0。
hInstance:当前实例句柄,用WinMain中的形参hInstance为其赋值。
hIcon:指定窗口类的图标句柄,设置为NULL,则使用默认图标,也可用如下函数进行赋值:

HICON LoadIcon(HINSTANCE hInstance, LPCTSTR lpIconName);
如:LoadIcon(NULL, IDI_WARNING); //第一个参数为NULL,加载系统默认图标

hCursor:指定窗口类的光标句柄,设置为NULL,则使用默认图标,也可用如下函数进行赋值:

HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName);
如:LoadCursor(NULL, IDC_HELP); //第一个参数为NULL,加载系统默认光标

hbrBackground:指示窗口的背景颜色,可用如下函数进行赋值:

HGDIOBJ GetStockObject(int fnObject);
如:GetStockObject(WHITE_BRUSH);

lpszMenuName:指定菜单资源的名字。如果设置为NULL,那么基于这个窗口类创建的窗口将没有默认菜单。
lpszClassName:指定窗口类的名字。

示例代码如下:

WNDCLASS wc;	//窗口类变量
	wc.cbClsExtra = 0;	//类附加内存
	wc.cbWndExtra = 0;	//窗口附加内存
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //背景色为白色
	wc.hCursor = (HCURSOR)LoadCursor(NULL, IDC_HELP);	//帮助光标
	wc.hIcon = (HICON)LoadIcon(NULL, IDI_WARNING);	//警告图标
	wc.hInstance = hInstance;	//应用程序实例,为WinMain第1个形参
	wc.lpfnWndProc = WinProc;	//窗口过程函数名字
	wc.lpszClassName = TEXT("MyWin");	//类的名字
	wc.lpszMenuName = NULL;	//没有菜单
	wc.style = 0;	//类的风格,填0,使用默认风格
注册窗口类

设计完窗口类(WNDCLASS)后, 需要调用RegisterClass函数对其进行注册,注册成功后,才可以创建该类型的窗口。
注册函数的原型声明如下:

ATOM RegisterClass(CONST WNDCLASS *lpWndClass);
使用示例:RegisterClass(&wc);
创建窗口

设计好窗口类并且将其成功注册之后, 即可用CreateWindow函数产生这种类型的窗口了。
CreateWindow函数的原型声明如下:

HWND CreateWindow(
		LPCTSTR lpClassName,
		LPCTSTR lpWindowName,
		DWORD dwStyle,
		int x,
		int y,
		int nWidth,
		int nHeight,
		HWND hWndParent,
		HMENU hMenu,
		HINSTANCE hInstance,
		LPVOID lpParam);

参数说明:
lpClassName:指定窗口类的名称,此名字必须和WNDCLASS的lpszClassName成员指定的名称一样。
lpWindowName:指定窗口的名字,即窗口的标题。
dwStyle:指定创建的窗口的样式,常指定为指WS_OVERLAPPEDWINDOW类型,这是一种多种窗口类型的组合类型。
x, y:指定窗口左上角的x,y坐标。如果参数x被设为CW_USEDEFAULT,那么系统为窗口选择默认的左上角坐标并忽略y参数。
nWidth,nHeight:指定窗口窗口的宽度,高度。如果参数nWidth被设为 CW_USEDEFAULT,那么系统为窗口选择默认的宽度和高度,参数nHeight被忽略。
hWndParent:指定被创建窗口的父窗口句柄,没有父窗口,则设置NULL。
hMenu:指定窗口菜单的句柄,没有,则设置为NULL。
hInstance:窗口所属的应用程序实例的句柄,用WinMain中的形参hInstance为其赋值。
lpParam:作为WM_CREATE消息的附加参数lParam传入的数据指针。通常设置为NULL。

返回值说明:如果窗口创建成功,CreateWindow函数将返回系统为该窗口分配的句柄,否则,返回NULL。

示例代码:

HWND  hWnd = CreateWindow(
TEXT("MyWin"),  //窗口类名字
TEXT("测试"),      //窗口标题
WS_OVERLAPPEDWINDOW,  //窗口风格	
CW_USEDEFAULT, CW_USEDEFAULT,  //窗口x,y坐标,使用默认值
CW_USEDEFAULT, CW_USEDEFAULT,  //窗口宽度,高度,使用默认值
NULL,  	//无父窗口
NULL, 	//无菜单
hInstance, 	//应用程序实例句柄,为WinMain第1个形参
NULL);	//附件信息,通常设置为NULL
显示及更新窗口

显示窗口函数原型:

BOOL ShowWindow(HWND hWnd, int nCmdShow);

更新窗口函数原型:

BOOL UpdateWindow(HWND hWnd);

示例代码:

ShowWindow(hWnd, SW_SHOWNORMAL); //SW_SHOWNORMAL为普通模式
	UpdateWindow(hWnd);

消息循环

在创建窗口、显示窗口、更新窗口后,我们需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。

消息结构体

在Windows程序中,消息是由MSG结构体来表示的。MSG结构体的定义如下:

typedef struct tagMSG {
	HWND hWnd;   
	UINT message;   
	WPARAM wParam;
	LPARAM lParam;   
	DWORD time;   
	POINT pt;
} MSG;

hWnd:消息所属的窗口。我们通常开发的程序都是窗口应用程序,一个消息一般都是与某个窗口相关联的。例如,在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。
message:消息的标识符,是由一个数值来表示的,不同的消息对应不同的数值。Windows将消息对应的数值定义为WM_XXX宏(WM是Windows Message的缩写)的形式, XXX对应某种消息的英文拼写的大写形式。例如,鼠标左键按下消息是WM_LBUTTONDOWN,键盘按下消息是WM_KEYDOWN,字符消息是 WM_CHAR……。
wParam: 指定消息的附加信息,如键盘按下会触发WM_KEYDOWN消息,但是,具体按下哪个按键需要wParam区分。
lParam:指定消息的附加信息,如鼠标左击会触发WM_LBUTTONDOWN消息,但是,具体点击的坐标需要lParam区分。
time:标识一个消息产生时的时间。
pt:表示产生这个消息时光标或鼠标的坐标。

取消息

要从消息队列中取出消息,我们需要调用GetMessage()函数,该函数的原型声明如下:

BOOL GetMessage(
	LPMSG lpMsg,
	HWND hWnd,
	UINT wMsgFilterMin,
	UINT wMsgFilterMax);

参数说明:
lpMsg:指向一个消息结构体(MSG),GetMessage从线程的消息队列中取出的消息信息将保存在该结构体变量中。
hWnd:指定接收属于哪一个窗口的消息。通常我们将其设置为NULL,用于接收属于调用线程的所有窗口的窗口消息。
wMsgFilterMin:指定消息的最小值。
wMsgFilterMax:指定消息的最大值。如果wMsgFilterMin和wMsgFilterMax都设置为0, 则接收所有消息。
返回值说明:GetMessage函数接收到除 WM_QUIT 外的消息均返回非零值。对于WM_QUIT消息,该函数返回零。如果出现了错误,该函数返回-1,例如,当参数hWnd是无效的窗口句柄或lpMsg是无效的指针时。

建立消息循环
MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

TranslateMessage:用于翻译、处理和转换消息并把新消息投放到消息队列中,并且此过程不会影响原来的消息队列。
DispatechMessage:用于把收到的消息传到窗口回调函数进行分析和处理。即将消息传递给操作系统,让操作系统调用窗口回调函数,来对信息进行处理。

消息处理机制

MFC---Windows消息机制_Windows_09

①操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中。
②应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理,例如,放弃对某些消息的响应,或者调用TranslateMessage产生新的消息。
③应用程序调用DispatchMessage,将消息回传给操作系统。消息是由 MSG结构体对象来表示的,其中就包含了接收消息的窗口的句柄。因此, DispatchMessage函数总能进行正确的传递。
④系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理。

窗口过程函数(消息处理函数)

在完成上述步骤后,剩下的工作就是编写一个窗口过程函数,用于处理发送给窗口的消息。
窗口过程函数的名字可以随便取, 如WinProc, 但函数定义的形式必须和下面声明的形式相同:

LRESULT CALLBACK WinProc( //CALLBACK 和WINAPI 作用一样
	HWND hWnd,		//信息所属的窗口句柄
	UINT uMsg,		//消息类型
	WPARAM wParam,	//附加信息(如键盘哪个键按下)
	LPARAM lParam	//附加信息(如鼠标点击坐标)
	);

示例代码:

LRESULT CALLBACK WinProc(
	HWND hWnd,		//信息所属的窗口句柄
	UINT uMsg,		//消息类型
	WPARAM wParam,	//附加信息(如键盘按键)
	LPARAM lParam	//附加信息(如鼠标点击坐标)
)
{
	switch (uMsg)
	{
	case WM_KEYDOWN: //键盘按下
	{
		MessageBox(hWnd, TEXT("键盘按下"), TEXT("键盘按下"), MB_OK);
		break;
	}
	case WM_LBUTTONDOWN: //鼠标左键按下
	{
		int xPos = LOWORD(lParam);
		int yPos = HIWORD(lParam);

		char buf[1024];
		wsprintf(buf, TEXT("x=%d,y=%d"), xPos, yPos);

		MessageBox(hWnd, buf, TEXT("鼠标左键按下"), MB_OK);
		break;
	}
	case WM_PAINT: //绘图事件
	{
		PAINTSTRUCT ps;//绘图结构体
		HDC hdc = BeginPaint(hWnd, &ps);
		TextOut(hdc, 100, 100, TEXT("HELLO"), strlen("HELLO"));
		EndPaint(hWnd, &ps);
		break;
	}
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	case WM_CLOSE:
		//所有xxWindow为结尾的方法,都不会进入到消息队列中,而是直接执行
		DestroyWindow(hWnd);//DestoryWindow 发送另一个消息 WM_DESTROY
		break;
	default:
		//以windows默认方式处理
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
		break;
	}
	return 0;
}

DefWindowProc函数:DefWindowProc函数调用默认的窗口过程,对应用程序没有处理的其他消息提供默认处理。
WM_CLOSE:对WM_CLOSE消息的响应并不是必须的,如果应用程序没有对该消息进行响应,系统将把这条消息传给DefWindowProc函数而 DefWindowProc函数则调用DestroyWindow函数来响应这条WM_CLOSE消息。
WM_DESTROY:DestroyWindow函数在销毁窗口后,会给窗口过程发送 WM_DESTROY消息,我们在该消息的响应代码中调用PostQuitMessage函数。
PostQuitMessage函数向应用程序的消息队列中投递一条WM_QUIT消息并返回。
**WinMain函数中,GetMessage 函数只有在收到WM_QUIT消息时才返回0,此时消息循环才结束,程序退出。**传递给 PostQuitMessage函数的参数值将作为WM_QUIT消息的wParam参数,这个值通常用做WinMain函数的返回值。

完整实例代码

#include<Windows.h>//底层实现窗口的头文件

//6、窗口过程函数(消息处理函数)
LRESULT CALLBACK WinProc(
	HWND hWnd,		//信息所属的窗口句柄
	UINT uMsg,		//消息类型
	WPARAM wParam,	//附加信息(如键盘按键)
	LPARAM lParam	//附加信息(如鼠标点击坐标)
)
{
	switch (uMsg)
	{
	case WM_KEYDOWN: //键盘按下
	{
		MessageBox(hWnd, TEXT("键盘按下"), TEXT("键盘按下"), MB_OK);
		break;
	}
	case WM_LBUTTONDOWN: //鼠标左键按下
	{
		int xPos = LOWORD(lParam);
		int yPos = HIWORD(lParam);

		char buf[1024];
		wsprintf(buf, TEXT("x=%d,y=%d"), xPos, yPos);

		MessageBox(hWnd, buf, TEXT("鼠标左键按下"), MB_OK);
		break;
	}
	case WM_PAINT: //绘图事件
	{
		PAINTSTRUCT ps;//绘图结构体
		HDC hdc = BeginPaint(hWnd, &ps);
		TextOut(hdc, 100, 100, TEXT("HELLO"), strlen("HELLO"));
		EndPaint(hWnd, &ps);
		break;
	}
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	case WM_CLOSE:
		//所有xxWindow为结尾的方法,都不会进入到消息队列中,而是直接执行
		DestroyWindow(hWnd);//DestoryWindow 发送另一个消息 WM_DESTROY
		break;
	default:
		//以windows默认方式处理
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
		break;
	}
	return 0;
}


//程序入口函数
int WINAPI WinMain(
	HINSTANCE hInstance,	//应用程序实例句柄
	HINSTANCE hPrevInstance,	//上一个应用程序句柄,在win32环境下,参数一般为null,不起作用
	LPSTR lpCmdLine,		//命令行参数char * argv[]
	int nShowCmd)	//窗口显示的样式 最大化 最小化  正常
{
	//1、设计一个窗口类
	WNDCLASS wc;	//窗口类变量
	wc.cbClsExtra = 0;	//类附加内存
	wc.cbWndExtra = 0;	//窗口附加内存
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); //背景色为白色
	wc.hCursor = (HCURSOR)LoadCursor(NULL, IDC_HELP);	//设置光标 如果第一个参数为NULL,代表使用系统提供的光标    帮助光标
	wc.hIcon = (HICON)LoadIcon(NULL, IDI_WARNING);	//设置光标 如果第一个参数为NULL,代表使用系统提供的图标  警告图标
	wc.hInstance = hInstance;	//应用程序实例,为WinMain第1个形参
	wc.lpfnWndProc = WinProc;	//回调函数  窗口过程函数名字
	wc.lpszClassName = TEXT("MyWin");	//类的名字
	wc.lpszMenuName = NULL;	//没有菜单
	wc.style = 0;	//类的风格,填0,使用默认风格

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

	//3、创建窗口
	HWND  hWnd = CreateWindow(
		TEXT("MyWin"),  //窗口类名字
		TEXT("测试"),      //窗口标题
		WS_OVERLAPPEDWINDOW,  //窗口风格	
		CW_USEDEFAULT, CW_USEDEFAULT,  //窗口x,y坐标,使用默认值
		CW_USEDEFAULT, CW_USEDEFAULT,  //窗口宽度,高度,使用默认值
		NULL,  	//无父窗口
		NULL, 	//无菜单
		hInstance, 	//应用程序实例句柄,为WinMain第1个形参
		NULL);	//附件信息,通常设置为NULL

	//4、显示及更新窗口
	ShowWindow(hWnd, SW_SHOWNORMAL); //SW_SHOWNORMAL为普通模式
	UpdateWindow(hWnd);


	//5、建立消息循环
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return 0;
}

MFC---Windows消息机制_windows_10


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
5Z6Aj0LQwRjK