今天来给大家详细剖析一下Windows的消息机制
一、什么是消息机制
首先消息机制是Windows上面进程之间通信的一种方式,除此之外还包括共享内存,管道,socket等等进程之间的通信方式,当然socket还可以实现远程进程之间的通信,这里就不再赘述。
1、动作
动作就是指用户做出了什么样的行为,当然也可以是系统做出的,比如用户点击了鼠标左键,或者敲击了键盘上面的按键"k",这都是一些具体的动作,如果有基础的朋友应该知道不论是点击鼠标还是按下键盘都会触发一个外部中断,这将使得CPU变态,从用户态转为内核态,进入操作系统的内核区执行中断处理的代码,当然其中有一步操作就是记录下来用户所执行的操作。
2、消息
消息就是把动作有关的信息封装到一个结构体MSG当中,这个MSG就是消息。
官方定义:
The message structure contains message information from a threads' message queue.
消息结构体定义:
typedef struct tagMSG {
HWND hwnd; //发送给哪个窗口
UINT message; //消息的类型(按下鼠标左键/按下键盘"k")
WPARAM wParam; //对message的进一步说明
LPARAM lParam; //对message的进一步说明
DWORD time; //事件被触发的时间
POINT pt; //动作发生的位置
} MSG, *PMSG;
参数详解:
HWND hwnd; //发送给哪个窗口
UINT message; //消息的类型(按下鼠标左键/按下键盘"k")
WPARAM wParam; //对message的进一步说明
LPARAM lParam; //对message的进一步说明
DWORD time; //事件被触发的时间
POINT pt; //动作发生的位置
二、消息机制的原理
1、消息机制的完整过程:
通过点击鼠标左键,触发了一个中断,产生了一个消息 》 Windows将这个消息保存在系统队列当中 》 操作系统将系统队列当中的消息MSG挂到应用进程的消息队列(位于内核区) 》应用进程通过消息循环来取出消息,如果是自己关心的就去处理,如果不是,就交给操作系统来处理
2、Windows窗口程序创建的全过程
1)第一步写出特定格式的窗口程序的主函数WinMain,其实就是C语言当中的main
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
hAppInstance = hInstance;
return 0;
}
2)注册窗口类
因为Windows系统默认只认识系统定义好的类,比如按钮,对话框之类的,如果我们需要自定义窗口就需要有自定义的类,并通过RegisterClass注册类来让系统识别,这里需要注意:
①注册类的以下4个带注释的是必须要填写的属性
②希望读者自己去逆向分析一下,为什么这里写的是WNDCLASS wndclass={0};直接写WNDCLASS wndclass;可不可以?这两种写法有什么区别?
//窗口的类名
PSTR className = (PCHAR)"My First Window";
// 创建窗口类的对象
WNDCLASS wndclass = { 0 }; //一定要先将所有值赋值
wndclass.hbrBackground = (HBRUSH)COLOR_MENU; //窗口的背景色
wndclass.hCursor = LoadCursor(NULL, IDC_APPSTARTING);
wndclass.lpfnWndProc = WindowProc; //窗口过程函数
wndclass.lpszClassName = className; //窗口类的名字
wndclass.hInstance = hInstance; //定义窗口类的应用程序的实例句柄
// 注册窗口类
// 参加MSDN文档RegisterClass->Parameters:
// You must fill the structure with the appropriate class attributes
// before passing it to the function.
RegisterClass(&wndclass);
3)创建窗口
①这里第一个参数使用的是类名是统一规划好的,为了方便和系统自定义的窗口类相统一
②从这里我们也可以看出来一个进程不一定只有一个窗口,它可以有很多子窗口,说到底窗口这种东西就是进程的资源罢了,可以有很多的,就像给进程分配多少个设备是一回事
// 创建窗口
HWND hwnd = CreateWindow(
className, //类名
"我的第一个窗口", //窗口标题
WS_OVERLAPPEDWINDOW, //窗口外观样式
10, //相对于父窗口的X坐标
10, //相对于父窗口的Y坐标
600, //窗口的宽度
300, //窗口的高度
NULL, //父窗口句柄,为NULL
NULL, //菜单句柄,为NULL
hInstance, //当前应用程序的句柄
NULL); //附加数据一般为NULL
if (hwnd == NULL) //是否创建成功
return 0;
4)显示窗口
调用Windows API ::ShowWindow来把窗口画出来
// 显示窗口
ShowWindow(hwnd, SW_SHOW);
// 更新窗口
UpdateWindow(hwnd);
5)消息处理
!!!这一部分是窗口程序的核心,我们的最终目的还是处理消息
①通过消息循环来循环查询系统队列当中有没有新的消息产生,如果有,就会把消息保存在out类型的参数MSG当中,然后翻译完消息之后再次DispatchMessage传给操作系统,交给操作系统来处理这消息。
②!!!这里一定要注意,我们的消息处理函数WindowProc不是应用程序自己调用的,而是操作系统帮我们调用的,其实很好理解,你前面这一句:wndclass.lpfnWndProc = WindowProc;不就是把函数地址扔给OS了吗?它直接调用不就行了,,有的童鞋说,那万一我写个死循环不把OS给搞死了吗?其实你想多了,你可以把OS理解成一个大进程,它只是单起了一个内核级线程来调用你的消息处理函数,卡死了,等你的时间片用完,就把你挂到阻塞队列里面,如果你出bug了,不还是会触发中断吗?触发中断谁来接管?还是OS啊!所以啊,童鞋,别想太多辣!😂😂😂
③这里再多一下嘴(Tandy还是那么爱你们,我哭死) ,有的同学不知道什么是回调函数,其实通过这个例子就很好理解啊,我自己定义自己调用的函数就是普通函数,但是如果我自己定义,由别的进程来调用就是你回调函数呗🙂🙂
// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
6)消息处理函数
这里还是强调上面那一点:
我们自定义的消息处理函数WindowProc不是应用程序自己调用的,而是操作系统帮我们调用的,当然了消息处理函数要遵循微软的规范,他的格式如下所示:
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
switch (uMsg)
{
//窗口消息
case WM_CREATE:
{
DbgPrintf("WM_CREATE %d %d\n", wParam, lParam);
CREATESTRUCT* createst = (CREATESTRUCT*)lParam;
DbgPrintf("CREATESTRUCT %s\n", createst->lpszClass);
break;
}
case WM_MOVE:
{
DbgPrintf("WM_MOVE %d %d\n", wParam, lParam);
POINTS points = MAKEPOINTS(lParam);
DbgPrintf("X Y %d %d\n", points.x, points.y);
break;
}
case WM_SIZE:
{
DbgPrintf("WM_SIZE %d %d\n", wParam, lParam);
int newWidth = (int)(short)LOWORD(lParam);
int newHeight = (int)(short)HIWORD(lParam);
DbgPrintf("WM_SIZE %d %d\n", newWidth, newHeight);
break;
}
case WM_DESTROY:
{
DbgPrintf("WM_DESTROY %d %d\n", wParam, lParam);
PostQuitMessage(0);
return 0;
break;
}
//键盘消息
case WM_KEYUP:
{
DbgPrintf("WM_KEYUP %d %d\n", wParam, lParam);
break;
}
case WM_KEYDOWN:
{
DbgPrintf("WM_KEYDOWN %d %d\n", wParam, lParam);
break;
}
//鼠标消息
case WM_LBUTTONDOWN:
{
DbgPrintf("WM_LBUTTONDOWN %d %d\n", wParam, lParam);
POINTS points = MAKEPOINTS(lParam);
DbgPrintf("WM_LBUTTONDOWN %d %d\n", points.x, points.y);
break;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
哦,对了,再补充一点,最后一定要写一个return 0;因为操作系统调用你这个函数的位置需要一个返回值,为0就代表这个消息已经处理完毕了,否则会陷入混乱,你可以仔细体会一下。、
最后我还想讲一下我们的程序为什么能够被CPU执行,如果你对上面哪里有疑问,说不定这一句话可以给你一点帮助呢:
首先我们打开计算机的电源键 》CPU从固定的ROM区开始执行操作系统加载程序 》操作系统启动之后开始帮助计算机管理软硬件资源的分配和使用,然后开始创建用户进程 》再为用户进程创建多个线程(核心级/用户级) 》然后操作系统就要主动让出CPU了,直到中断/异常的出现才会重新执行操作系统的代码 》当一个核心级线程阻塞之后(时间片用完/等待某个事件),会通过中断/异常进入OS,来重新调度新的内核级线程 》切换到另外一个线程去执行。
三、完整源码
#include<iostream>
using namespace std;
#include<Windows.h>
#pragma warning(disable:4996)
void __cdecl OutputDebugStringF(const char* format, ...)
{
va_list vlArgs;
char* strBuffer = (char*)GlobalAlloc(GPTR, 4096);
va_start(vlArgs, format);
_vsnprintf(strBuffer, 4096 - 1, format, vlArgs);
va_end(vlArgs);
strcat(strBuffer, "\n");
OutputDebugStringA(strBuffer);
GlobalFree(strBuffer);
return;
}
#define DbgPrintf OutputDebugStringF
HINSTANCE hAppInstance;
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
);
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
hAppInstance = hInstance;
//窗口的类名
PSTR className = (PCHAR)"My First Window";
// 创建窗口类的对象
WNDCLASS wndclass = { 0 }; //一定要先将所有值赋值
wndclass.hbrBackground = (HBRUSH)COLOR_MENU; //窗口的背景色
wndclass.hCursor = LoadCursor(NULL, IDC_APPSTARTING);
wndclass.lpfnWndProc = WindowProc; //窗口过程函数
wndclass.lpszClassName = className; //窗口类的名字
wndclass.hInstance = hInstance; //定义窗口类的应用程序的实例句柄
// 注册窗口类
// 参加MSDN文档RegisterClass->Parameters:
// You must fill the structure with the appropriate class attributes
// before passing it to the function.
RegisterClass(&wndclass);
// 创建窗口
HWND hwnd = CreateWindow(
className, //类名
"我的第一个窗口", //窗口标题
WS_OVERLAPPEDWINDOW, //窗口外观样式
10, //相对于父窗口的X坐标
10, //相对于父窗口的Y坐标
600, //窗口的宽度
300, //窗口的高度
NULL, //父窗口句柄,为NULL
NULL, //菜单句柄,为NULL
hInstance, //当前应用程序的句柄
NULL); //附加数据一般为NULL
if (hwnd == NULL) //是否创建成功
return 0;
// 显示窗口
ShowWindow(hwnd, SW_SHOW);
// 更新窗口
UpdateWindow(hwnd);
// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(
IN HWND hwnd,
IN UINT uMsg,
IN WPARAM wParam,
IN LPARAM lParam
)
{
switch (uMsg)
{
//窗口消息
case WM_CREATE:
{
DbgPrintf("WM_CREATE %d %d\n", wParam, lParam);
CREATESTRUCT* createst = (CREATESTRUCT*)lParam;
DbgPrintf("CREATESTRUCT %s\n", createst->lpszClass);
break;
}
case WM_MOVE:
{
DbgPrintf("WM_MOVE %d %d\n", wParam, lParam);
POINTS points = MAKEPOINTS(lParam);
DbgPrintf("X Y %d %d\n", points.x, points.y);
break;
}
case WM_SIZE:
{
DbgPrintf("WM_SIZE %d %d\n", wParam, lParam);
int newWidth = (int)(short)LOWORD(lParam);
int newHeight = (int)(short)HIWORD(lParam);
DbgPrintf("WM_SIZE %d %d\n", newWidth, newHeight);
break;
}
case WM_DESTROY:
{
DbgPrintf("WM_DESTROY %d %d\n", wParam, lParam);
PostQuitMessage(0);
return 0;
break;
}
//键盘消息
case WM_KEYUP:
{
DbgPrintf("WM_KEYUP %d %d\n", wParam, lParam);
break;
}
case WM_KEYDOWN:
{
DbgPrintf("WM_KEYDOWN %d %d\n", wParam, lParam);
break;
}
//鼠标消息
case WM_LBUTTONDOWN:
{
DbgPrintf("WM_LBUTTONDOWN %d %d\n", wParam, lParam);
POINTS points = MAKEPOINTS(lParam);
DbgPrintf("WM_LBUTTONDOWN %d %d\n", points.x, points.y);
break;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
运行结果截图:
以上就是本节的全部的内容了,喜欢的话记得一键三连支持一下哦亲~💗💗💗🤞🤞