Windows消息机制

1. 前言

Windows系统是基于消息机制的。 操作系统有一个系统消息队列, 应用程序每个GUI线程有一个线程消息队列(没有所谓的进程消息队列), 线程一开始创建的时候并没有消息队列,只有线程第一次调用GDI函数(User32.dll或者gdi32.dll中的函数时),系统才会为它创建消息队列,也就是非GUI线程是没有消息队列的,同时一个线程也只有一个消息队列,但可以有多个窗口, 这些窗口共用一个消息队列,正常UI线程会启动一个消息循环,不断从线程消息队列中取出消息交给窗口过程函数WndProc去处理.

在Windows中一个消息有6个部分, (需要形成肌肉记忆一样记住)

struct MSG
{
    HWND hWnd;         // 产生消息的窗口的句柄
    UINT UMsgId;       // 消息ID
    WPARAM wParam;     // 附带参数
    LPARAM lParam;     // 附带参数
    DWORD time;        // 消息产生的时间
    POINT pt;          // 消息产生时在屏幕上的坐标
}

一个简单的消息循环如下:

MSG msg;
BOOL bRet = GetMessage(&msg, hWnd, 0, 0);
while (bRet != -1 && bRet != 0 )
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    bRet = GetMessage(&msg, hWnd, 0, 0);
}

改进后的消息循环:

MSG msg;
while (1)
{
    if (PeekMessage(&msg, hWnd, 0, 0, PM_NOREMOVE))
    {
        if (GetMessage(&msg, hWnd, 0, 0) > 0)
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        else
            break;
        }
        else
        {
		//other code 空闲处理
        }
}

2. 相关API函数

2.1 GetMessage

2.2 sendMessage

该函数可以将一个消息发送到指定的窗口中,直到窗口处理函数处理完成后才会返回,所以此函数为阻塞函数。

2.3 PostMessage

该函数可以将一个消息投递到与指定窗口创建的线程相关联的消息队列中,不等待线程处理消息就直接返回,属于异步消息模式。

两者区别: 一个同步,一个异步, SendMessage直接发送到窗口处理函数, PostMessage发送到窗口的消息队列

2.4 PostThreadMessage

3. 消息分类

Windows中有三类消息。分别是标准消息,命令消息,通告消息。

3.1 标准消息

所有以WM_开头的消息就是标注消息(WM_COMMAND消息除外)。标准消息可以是系统定义的,也可以是用户自定义的,只要ID大于WM_USER就可以了。

3.1.1 WM_NCCREATE

NC的全称是NoClient, 在调用CreateWindowEx时,会直接发送到窗口过程函数, 如果处理这个消息应该返回TRUE, 否则CreateWindowEx返回一个空句柄。
wParam:无附带信息。
lParam:指向CREATESTRUCT结构体的指针,其中包含正在创建的窗口信息。

3.1.2 WM_CREATE

在WM_NCCREATE之后和CreateWindowEx返回之前产生, 该消息直接被发送到窗口过程函数。一般在此消息中初始化菜单、按钮、滚动条、子窗口等。
wParam:无附带信息。
lParam:指向CREATESTRUCT结构体的指针,在初始化子类控件时能用到。

3.1.3 WM_DROPFILES

在调用CreateWindowEx函数时设置了窗口扩展属性WS_ACCEPTFILES, 用户将文件拖拽到窗口上就会产生这个消息。
wParam:指向HDROP对象的指针。
lParam:无附带消息。
对于此消息,一般处理过程是首先获取拖拽文件的数量,然后获得拖拽文件的路径,最后确定具体操作。 要通过DragQueryFileA函数来实现.

case WM_DROPFILES:
 {
     HDROP hDrop = reinterpret_cast<HDROP>(wParam);
     UINT uFileCount = DragQueryFileA(hDrop, 0xFFFFFFFF, NULL, 0); //获取文件数量

     char filepath[MAX_PATH];
     for (UINT i = 0; i < uFileCount; i++)               
     {
         if(DragQueryFileA(hDrop, i, filepath, MAX_PATH))         //遍历获得文件路径
             DropFiles.push_back(std::string{filepath});
     }
     DragFinish(hDrop);

     for (const std::string& name : DropFiles)    //这里打印输出一下.
     {
         OutputDebugStringA(name.c_str());
         OutputDebugStringA("\n");
     }
      
 }break;

3.1.4 WM_CLOSE

当用户点击窗口上x关闭按钮或者点击系统菜单中关闭按钮时会产生这个消息。对于该消息的自定义处理是弹出一个对话框询问用户是否真的关闭窗口,程序的默认处理是调用DestroyWindow函数来关闭窗口。
wParam: 无附带信息
lParam: 无附带信息

3.1.5 WM_DESTROY

调用DestroyWindow函数会产生该消息。
wParam:无附带信息
lParam:没附带信息。
通常在这个消息中进行资源清理操作,如释放内存,关闭句柄等等。最后发送WM_QUIT消息。

LRESULT CALLBACK WndProc(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam)
{
    switch (umsg)
    {
    case WM_DESTROY:
        // 进行资源清理操作,例如释放内存、关闭文件等
        // 这里只是示例,实际应用中根据具体情况进行资源清理
        // .....
        PostQuitMessage(0); // 发送 WM_QUIT 消息,退出消息循环
        break;
    default:
        return DefWindowProc(hwnd, umsg, wparam, lparam);
    }
    return 0;
}

消息顺序如下:

3.2 命令消息

来自菜单、工具栏按钮或者加速键(就是快捷键)的消息。以WM_COMMAND呈现。

3.3 通告消息:

由控件产生的消息,比如按钮的单机、列表框的选择都会产生这类消息,目的是向父窗口通知事件发生。

主要有两类消息, 系统消息和用户自定义消息. 系统消息是系统定义好的消息, ID是0 ~ 0x03FF. 用户自定义消息ID是0x0400 ~ 0x7FFF; 用户自定义消息宏WM_USER

系统消息是我们发的不需要我们处理, 需要我们处理的不需要我们发,比如WM_QUIT消息. 用户自定义消息既需要我们发送,有需要我们处理.

3.5 WM_COMMAND

命令消息,它由菜单、加速键或者工具栏按钮产生。
LOWORD(wParam)可以得到菜单或者加速键的ID。菜单分割条的ID是0。